]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/tree/ResourceTree.js
d/control: bump versioned dependency for ifupdown2
[pve-manager.git] / www / manager6 / tree / ResourceTree.js
CommitLineData
5289a1b8 1/*
ec505260 2 * Left Treepanel, containing all the resources we manage in this datacenter: server nodes, server storages, VMs and Containers
5289a1b8 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
a19652db 71 return n1.id > n2.id ? 1 : (n1.id < n2.id ? -1 : 0);
83726b9d
DM
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
a19652db
DC
109 // add additional elements to text
110 // at the moment only the usage indicator for storages
111 setText: function(info) {
112 var me = this;
113
114 var status = '';
115 if (info.type === 'storage') {
116 var maxdisk = info.maxdisk;
117 var disk = info.disk;
118 var usage = disk/maxdisk;
119 var cls = '';
120 if (usage <= 1.0 && usage >= 0.0) {
121 var height = (usage*100).toFixed(0);
122 var neg_height = (100-usage*100).toFixed(0);
123 status = '<div class="usage-wrapper">';
124 status += '<div class="usage-negative" style="height: ';
125 status += neg_height + '%"></div>';
126 status += '<div class="usage" style="height: '+ height +'%"></div>';
127 status += '</div> ';
128 }
129 }
130
131 info.text = status + info.text;
132 },
133
dca23c54
DC
134 setToolTip: function(info) {
135 if (info.type === 'pool' || info.groupbyid !== undefined) {
136 return;
137 }
138
139 var qtips = [gettext('Status') + ': ' + (info.qmpstatus || info.status)];
82a33d4e
TL
140 if (info.lock) {
141 qtips.push('Config locked (' + info.lock + ')');
142 }
dca23c54
DC
143 if (info.hastate != 'unmanaged') {
144 qtips.push(gettext('HA State') + ": " + info.hastate);
145 }
146
147 info.qtip = qtips.join(', ');
148 },
149
83726b9d
DM
150 // private
151 addChildSorted: function(node, info) {
152 var me = this;
153
154 me.setIconCls(info);
a19652db 155 me.setText(info);
dca23c54 156 me.setToolTip(info);
83726b9d
DM
157
158 var defaults;
159 if (info.groupbyid) {
0c686cac 160 info.text = info.groupbyid;
83726b9d
DM
161 if (info.type === 'type') {
162 defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
163 if (defaults && defaults.text) {
164 info.text = defaults.text;
165 }
166 }
167 }
a736e4b7 168 var child = Ext.create('PVETree', info);
83726b9d
DM
169
170 var cs = node.childNodes;
171 var pos;
172 if (cs) {
173 pos = cs[me.findInsertIndex(node, child, 0, cs.length)];
174 }
175
176 node.insertBefore(child, pos);
177
178 return child;
179 },
180
181 // private
182 groupChild: function(node, info, groups, level) {
183 var me = this;
184
185 var groupby = groups[level];
186 var v = info[groupby];
187
188 if (v) {
189 var group = node.findChild('groupbyid', v);
190 if (!group) {
191 var groupinfo;
192 if (info.type === groupby) {
193 groupinfo = info;
194 } else {
195 groupinfo = {
196 type: groupby,
197 id : groupby + "/" + v
198 };
199 if (groupby !== 'type') {
200 groupinfo[groupby] = v;
201 }
202 }
203 groupinfo.leaf = false;
204 groupinfo.groupbyid = v;
205 group = me.addChildSorted(node, groupinfo);
83726b9d
DM
206 }
207 if (info.type === groupby) {
208 return group;
209 }
210 if (group) {
211 return me.groupChild(group, info, groups, level + 1);
212 }
213 }
214
215 return me.addChildSorted(node, info);
216 },
217
218 initComponent : function() {
219 var me = this;
220
221 var rstore = PVE.data.ResourceStore;
222 var sp = Ext.state.Manager.getProvider();
223
224 if (!me.viewFilter) {
225 me.viewFilter = {};
226 }
227
228 var pdata = {
229 dataIndex: {},
230 updateCount: 0
231 };
232
233 var store = Ext.create('Ext.data.TreeStore', {
234 model: 'PVETree',
235 root: {
236 expanded: true,
237 id: 'root',
4dbc64a7
DC
238 text: gettext('Datacenter'),
239 iconCls: 'fa fa-server'
83726b9d
DM
240 }
241 });
242
243 var stateid = 'rid';
244
245 var updateTree = function() {
246 var tmp;
247
397dfdd3 248 store.suspendEvents();
83726b9d
DM
249
250 var rootnode = me.store.getRootNode();
83726b9d
DM
251 // remember selected node (and all parents)
252 var sm = me.getSelectionModel();
253
254 var lastsel = sm.getSelection()[0];
d5a7996b 255 var reselect = false;
83726b9d
DM
256 var parents = [];
257 var p = lastsel;
258 while (p && !!(p = p.parentNode)) {
259 parents.push(p);
260 }
261
262 var index = pdata.dataIndex;
263
264 var groups = me.viewFilter.groups || [];
265 var filterfn = me.viewFilter.filterfn;
266
fe64be90
EK
267 // remove vanished or moved items
268 // update in place changed items
83726b9d
DM
269 var key;
270 for (key in index) {
271 if (index.hasOwnProperty(key)) {
272 var olditem = index[key];
273
274 // getById() use find(), which is slow (ExtJS4 DP5)
275 //var item = rstore.getById(olditem.data.id);
276 var item = rstore.data.get(olditem.data.id);
277
278 var changed = false;
fe64be90 279 var moved = false;
83726b9d
DM
280 if (item) {
281 // test if any grouping attributes changed
fe64be90 282 // this will also catch migrated nodes
6ae31fc1 283 // in server view
83726b9d
DM
284 var i, len;
285 for (i = 0, len = groups.length; i < len; i++) {
286 var attr = groups[i];
287 if (item.data[attr] != olditem.data[attr]) {
288 //console.log("changed " + attr);
fe64be90 289 moved = true;
83726b9d
DM
290 break;
291 }
292 }
fe64be90 293
ec505260 294 // explicitly check for node, since
6ae31fc1
DC
295 // in some views, node is not a grouping
296 // attribute
297 if (!moved && item.data.node !== olditem.data.node) {
298 moved = true;
299 }
300
fe64be90 301 // tree item has been updated
6284a48a
DC
302 var fields = [
303 'text', 'running', 'template', 'status',
304 'qmpstatus', 'hastate', 'lock'
305 ];
306
307 var field;
308 for (i = 0; i < fields.length; i++) {
309 field = fields[i];
310 if (item.data[field] !== olditem.data[field]) {
311 changed = true;
312 break;
313 }
83726b9d
DM
314 }
315
316 // fixme: also test filterfn()?
317 }
318
d0e96c0d
EK
319 if (changed) {
320 olditem.beginEdit();
321 //console.log("REM UPDATE UID: " + key + " ITEM " + item.data.running);
322 var info = olditem.data;
323 Ext.apply(info, item.data);
324 me.setIconCls(info);
a19652db 325 me.setText(info);
dca23c54 326 me.setToolTip(info);
d0e96c0d
EK
327 olditem.commit();
328 }
fe64be90 329 if ((!item || moved) && olditem.isLeaf()) {
83726b9d 330 //console.log("REM UID: " + key + " ITEM " + olditem.data.id);
d0e96c0d
EK
331 delete index[key];
332 var parentNode = olditem.parentNode;
d5a7996b
DC
333 // when the selected item disappears,
334 // we have to deselect it here, and reselect it
335 // later
336 if (lastsel && olditem.data.id === lastsel.data.id) {
337 reselect = true;
338 sm.deselect(olditem);
339 }
340 // since the store events are suspended, we
341 // manually remove the item from the store also
342 store.remove(olditem);
d0e96c0d 343 parentNode.removeChild(olditem, true);
83726b9d
DM
344 }
345 }
346 }
347
348 // add new items
349 rstore.each(function(item) {
350 var olditem = index[item.data.id];
351 if (olditem) {
352 return;
353 }
354
355 if (filterfn && !filterfn(item)) {
356 return;
357 }
358
359 //console.log("ADD UID: " + item.data.id);
360
361 var info = Ext.apply({ leaf: true }, item.data);
362
363 var child = me.groupChild(rootnode, info, groups, 0);
364 if (child) {
365 index[item.data.id] = child;
366 }
367 });
368
0f3e4bc2 369 store.resumeEvents();
cc3bcee0 370 store.fireEvent('refresh', store);
0f3e4bc2 371
83726b9d
DM
372 // select parent node is selection vanished
373 if (lastsel && !rootnode.findChild('id', lastsel.data.id, true)) {
374 lastsel = rootnode;
375 while (!!(p = parents.shift())) {
376 if (!!(tmp = rootnode.findChild('id', p.data.id, true))) {
377 lastsel = tmp;
378 break;
379 }
380 }
381 me.selectById(lastsel.data.id);
d5a7996b
DC
382 } else if (lastsel && reselect) {
383 me.selectById(lastsel.data.id);
83726b9d
DM
384 }
385
d0e96c0d 386 // on first tree load set the selection from the stateful provider
83726b9d 387 if (!pdata.updateCount) {
83726b9d
DM
388 rootnode.expand();
389 me.applyState(sp.get(stateid));
390 }
391
392 pdata.updateCount++;
393 };
394
395 var statechange = function(sp, key, value) {
396 if (key === stateid) {
397 me.applyState(value);
398 }
399 };
400
401 sp.on('statechange', statechange);
402
403 Ext.apply(me, {
91994a49 404 allowSelection: true,
83726b9d
DM
405 store: store,
406 viewConfig: {
407 // note: animate cause problems with applyState
408 animate: false
409 },
410 //useArrows: true,
411 //rootVisible: false,
412 //title: 'Resource Tree',
413 listeners: {
685b7aa4 414 itemcontextmenu: PVE.Utils.createCmdMenu,
83726b9d
DM
415 destroy: function() {
416 rstore.un("load", updateTree);
91994a49 417 },
cc1a91be 418 beforecellmousedown: function (tree, td, cellIndex, record, tr, rowIndex, ev) {
d5a7996b 419 var sm = me.getSelectionModel();
91994a49 420 // disable selection when right clicking
d5a7996b
DC
421 // except the record is already selected
422 me.allowSelection = (ev.button !== 2) || sm.isSelected(record);
91994a49
DC
423 },
424 beforeselect: function (tree, record, index, eopts) {
425 var allow = me.allowSelection;
426 me.allowSelection = true;
427 return allow;
e3129443
DC
428 },
429 itemdblclick: PVE.Utils.openTreeConsole
83726b9d
DM
430 },
431 setViewFilter: function(view) {
432 me.viewFilter = view;
433 me.clearTree();
434 updateTree();
435 },
3d7b2aa9
TL
436 setDatacenterText: function(clustername) {
437 var rootnode = me.store.getRootNode();
438
439 var rnodeText = gettext('Datacenter');
440 if (clustername !== undefined) {
441 rnodeText += ' (' + clustername + ')';
442 }
443
444 rootnode.beginEdit();
445 rootnode.data.text = rnodeText;
446 rootnode.commit();
447 },
83726b9d
DM
448 clearTree: function() {
449 pdata.updateCount = 0;
450 var rootnode = me.store.getRootNode();
451 rootnode.collapse();
08801a5d 452 rootnode.removeAll();
83726b9d
DM
453 pdata.dataIndex = {};
454 me.getSelectionModel().deselectAll();
455 },
456 selectExpand: function(node) {
457 var sm = me.getSelectionModel();
458 if (!sm.isSelected(node)) {
459 sm.select(node);
460 var cn = node;
461 while (!!(cn = cn.parentNode)) {
462 if (!cn.isExpanded()) {
463 cn.expand();
464 }
465 }
9f950723 466 me.getView().focusRow(node);
83726b9d
DM
467 }
468 },
469 selectById: function(nodeid) {
470 var rootnode = me.store.getRootNode();
471 var sm = me.getSelectionModel();
472 var node;
473 if (nodeid === 'root') {
474 node = rootnode;
475 } else {
476 node = rootnode.findChild('id', nodeid, true);
477 }
478 if (node) {
479 me.selectExpand(node);
480 }
cfffc271 481 return node;
83726b9d 482 },
83726b9d
DM
483 applyState : function(state) {
484 var sm = me.getSelectionModel();
485 if (state && state.value) {
486 me.selectById(state.value);
487 } else {
488 sm.deselectAll();
489 }
490 }
491 });
492
493 me.callParent();
494
495 var sm = me.getSelectionModel();
496 sm.on('select', function(sm, n) {
497 sp.set(stateid, { value: n.data.id});
498 });
499
500 rstore.on("load", updateTree);
501 rstore.startUpdate();
502 //rstore.stopUpdate();
503 }
504
505});