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