]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/tree/ResourceTree.js
54c6403d80fa35a450641cdf7918339e0f45af78
2 * Left Treepanel, containing all the resources we manage in this datacenter: server nodes, server storages, VMs and Containers
4 Ext
.define('PVE.tree.ResourceTree', {
5 extend
: 'Ext.tree.TreePanel',
6 alias
: ['widget.pveResourceTree'],
8 userCls
: 'proxmox-tags-circle',
13 iconCls
: 'fa fa-building',
14 text
: gettext('Nodes'),
17 iconCls
: 'fa fa-tags',
18 text
: gettext('Resource Pool'),
21 iconCls
: 'fa fa-database',
22 text
: gettext('Storage'),
29 iconCls
: 'fa fa-desktop',
30 text
: gettext('Virtual Machine'),
33 //iconCls: 'x-tree-node-lxc',
34 iconCls
: 'fa fa-cube',
35 text
: gettext('LXC Container'),
38 iconCls
: 'fa fa-file-o',
46 nodeSortFn: function(node1
, node2
) {
48 let n1
= node1
.data
, n2
= node2
.data
;
50 if (!n1
.groupbyid
=== !n2
.groupbyid
) {
51 let n1IsGuest
= n1
.type
=== 'qemu' || n1
.type
=== 'lxc';
52 let n2IsGuest
= n2
.type
=== 'qemu' || n2
.type
=== 'lxc';
53 if (me
['group-guest-types'] || !n1IsGuest
|| !n2IsGuest
) {
54 // first sort (group) by type
55 if (n1
.type
> n2
.type
) {
57 } else if (n1
.type
< n2
.type
) {
62 // then sort (group) by ID
64 if (me
['group-templates'] && (!n1
.template
!== !n2
.template
)) {
65 return n1
.template
? 1 : -1; // sort templates after regular VMs
67 if (me
['sort-field'] === 'vmid') {
68 if (n1
.vmid
> n2
.vmid
) { // prefer VMID as metric for guests
70 } else if (n1
.vmid
< n2
.vmid
) {
74 return n1
.name
.localeCompare(n2
.name
);
77 // same types but not a guest
78 return n1
.id
> n2
.id
? 1 : n1
.id
< n2
.id
? -1 : 0;
79 } else if (n1
.groupbyid
) {
81 } else if (n2
.groupbyid
) {
84 return 0; // should not happen
87 // private: fast binary search
88 findInsertIndex: function(node
, child
, start
, end
) {
91 let diff
= end
- start
;
95 let mid
= start
+ (diff
>> 1);
97 let res
= me
.nodeSortFn(child
, node
.childNodes
[mid
]);
99 return me
.findInsertIndex(node
, child
, start
, mid
);
101 return me
.findInsertIndex(node
, child
, mid
+ 1, end
);
105 setIconCls: function(info
) {
106 let cls
= PVE
.Utils
.get_object_icon_class(info
.type
, info
);
112 // add additional elements to text. Currently only the usage indicator for storages
113 setText: function(info
) {
117 if (info
.type
=== 'storage') {
118 let usage
= info
.disk
/ info
.maxdisk
;
119 if (usage
>= 0.0 && usage
<= 1.0) {
120 let barHeight
= (usage
* 100).toFixed(0);
121 let remainingHeight
= (100 - barHeight
).toFixed(0);
122 status
= '<div class="usage-wrapper">';
123 status
+= `<div class="usage-negative" style="height: ${remainingHeight}%"></div>`;
124 status
+= `<div class="usage" style="height: ${barHeight}%"></div>`;
128 if (Ext
.isNumeric(info
.vmid
) && info
.vmid
> 0) {
129 if (PVE
.UIOptions
.getTreeSortingValue('sort-field') !== 'vmid') {
130 info
.text
= `${info.name} (${String(info.vmid)})`;
134 info
.text
+= PVE
.Utils
.renderTags(info
.tags
, PVE
.UIOptions
.tagOverrides
);
136 info
.text
= status
+ info
.text
;
139 setToolTip: function(info
) {
140 if (info
.type
=== 'pool' || info
.groupbyid
!== undefined) {
144 let qtips
= [gettext('Status') + ': ' + (info
.qmpstatus
|| info
.status
)];
146 qtips
.push(Ext
.String
.format(gettext('Config locked ({0})'), info
.lock
));
148 if (info
.hastate
!== 'unmanaged') {
149 qtips
.push(gettext('HA State') + ": " + info
.hastate
);
152 info
.qtip
= qtips
.join(', ');
156 addChildSorted: function(node
, info
) {
163 if (info
.groupbyid
) {
164 info
.text
= info
.groupbyid
;
165 if (info
.type
=== 'type') {
166 let defaults
= PVE
.tree
.ResourceTree
.typeDefaults
[info
.groupbyid
];
167 if (defaults
&& defaults
.text
) {
168 info
.text
= defaults
.text
;
172 let child
= Ext
.create('PVETree', info
);
174 if (node
.childNodes
) {
175 let pos
= me
.findInsertIndex(node
, child
, 0, node
.childNodes
.length
);
176 node
.insertBefore(child
, node
.childNodes
[pos
]);
178 node
.insertBefore(child
);
185 groupChild: function(node
, info
, groups
, level
) {
188 let groupBy
= groups
[level
];
189 let v
= info
[groupBy
];
192 let group
= node
.findChild('groupbyid', v
);
195 if (info
.type
=== groupBy
) {
200 id
: groupBy
+ "/" + v
,
202 if (groupBy
!== 'type') {
203 groupinfo
[groupBy
] = v
;
206 groupinfo
.leaf
= false;
207 groupinfo
.groupbyid
= v
;
208 group
= me
.addChildSorted(node
, groupinfo
);
210 if (info
.type
=== groupBy
) {
214 return me
.groupChild(group
, info
, groups
, level
+ 1);
218 return me
.addChildSorted(node
, info
);
221 saveSortingOptions: function() {
224 for (const key
of ['sort-field', 'group-templates', 'group-guest-types']) {
225 let newValue
= PVE
.UIOptions
.getTreeSortingValue(key
);
226 if (me
[key
] !== newValue
) {
234 initComponent: function() {
236 me
.saveSortingOptions();
238 let rstore
= PVE
.data
.ResourceStore
;
239 let sp
= Ext
.state
.Manager
.getProvider();
241 if (!me
.viewFilter
) {
250 let store
= Ext
.create('Ext.data.TreeStore', {
255 text
: gettext('Datacenter'),
256 iconCls
: 'fa fa-server',
262 const changedFields
= [
263 'text', 'running', 'template', 'status', 'qmpstatus', 'hastate', 'lock', 'tags',
266 let updateTree = function() {
267 store
.suspendEvents();
269 let rootnode
= me
.store
.getRootNode();
270 // remember selected node (and all parents)
271 let sm
= me
.getSelectionModel();
272 let lastsel
= sm
.getSelection()[0];
274 let sorting_changed
= me
.saveSortingOptions();
275 for (let node
= lastsel
; node
; node
= node
.parentNode
) {
279 let groups
= me
.viewFilter
.groups
|| [];
280 // explicitly check for node/template, as those are not always grouping attributes
281 // also check for name for when the tree is sorted by name
282 let moveCheckAttrs
= groups
.concat(['node', 'template', 'name']);
283 let filterfn
= me
.viewFilter
.filterfn
;
285 let reselect
= false; // for disappeared nodes
286 let index
= pdata
.dataIndex
;
287 // remove vanished or moved items and update changed items in-place
288 for (const [key
, olditem
] of Object
.entries(index
)) {
289 // getById() use find(), which is slow (ExtJS4 DP5)
290 let item
= rstore
.data
.get(olditem
.data
.id
);
292 let changed
= sorting_changed
, moved
= sorting_changed
;
294 // test if any grouping attributes changed, catches migrated tree-nodes in server view too
295 for (const attr
of moveCheckAttrs
) {
296 if (item
.data
[attr
] !== olditem
.data
[attr
]) {
302 // tree item has been updated
303 for (const field
of changedFields
) {
304 if (item
.data
[field
] !== olditem
.data
[field
]) {
309 // FIXME: also test filterfn()?
314 let info
= olditem
.data
;
315 Ext
.apply(info
, item
.data
);
321 if ((!item
|| moved
) && olditem
.isLeaf()) {
323 let parentNode
= olditem
.parentNode
;
324 // a selected item moved (migration) or disappeared (destroyed), so deselect that
325 // node now and try to reselect the moved (or its parent) node later
326 if (lastsel
&& olditem
.data
.id
=== lastsel
.data
.id
) {
328 sm
.deselect(olditem
);
330 // store events are suspended, so remove the item manually
331 store
.remove(olditem
);
332 parentNode
.removeChild(olditem
, true);
336 rstore
.each(function(item
) { // add new items
337 let olditem
= index
[item
.data
.id
];
341 if (filterfn
&& !filterfn(item
)) {
344 let info
= Ext
.apply({ leaf
: true }, item
.data
);
346 let child
= me
.groupChild(rootnode
, info
, groups
, 0);
348 index
[item
.data
.id
] = child
;
352 store
.resumeEvents();
353 store
.fireEvent('refresh', store
);
355 // select parent node if original selected node vanished
356 if (lastsel
&& !rootnode
.findChild('id', lastsel
.data
.id
, true)) {
358 for (const node
of parents
) {
359 if (rootnode
.findChild('id', node
.data
.id
, true)) {
364 me
.selectById(lastsel
.data
.id
);
365 } else if (lastsel
&& reselect
) {
366 me
.selectById(lastsel
.data
.id
);
369 // on first tree load set the selection from the stateful provider
370 if (!pdata
.updateCount
) {
372 me
.applyState(sp
.get(stateid
));
378 sp
.on('statechange', (_sp
, key
, value
) => {
379 if (key
=== stateid
) {
380 me
.applyState(value
);
385 allowSelection
: true,
388 animate
: false, // note: animate cause problems with applyState
391 itemcontextmenu
: PVE
.Utils
.createCmdMenu
,
392 destroy: function() {
393 rstore
.un("load", updateTree
);
395 beforecellmousedown: function(tree
, td
, cellIndex
, record
, tr
, rowIndex
, ev
) {
396 let sm
= me
.getSelectionModel();
397 // disable selection when right clicking except if the record is already selected
398 me
.allowSelection
= ev
.button
!== 2 || sm
.isSelected(record
);
400 beforeselect: function(tree
, record
, index
, eopts
) {
401 let allow
= me
.allowSelection
;
402 me
.allowSelection
= true;
405 itemdblclick
: PVE
.Utils
.openTreeConsole
,
407 setViewFilter: function(view
) {
408 me
.viewFilter
= view
;
412 setDatacenterText: function(clustername
) {
413 let rootnode
= me
.store
.getRootNode();
415 let rnodeText
= gettext('Datacenter');
416 if (clustername
!== undefined) {
417 rnodeText
+= ' (' + clustername
+ ')';
420 rootnode
.beginEdit();
421 rootnode
.data
.text
= rnodeText
;
424 clearTree: function() {
425 pdata
.updateCount
= 0;
426 let rootnode
= me
.store
.getRootNode();
428 rootnode
.removeAll();
429 pdata
.dataIndex
= {};
430 me
.getSelectionModel().deselectAll();
432 selectExpand: function(node
) {
433 let sm
= me
.getSelectionModel();
434 if (!sm
.isSelected(node
)) {
436 for (let iter
= node
; iter
; iter
= iter
.parentNode
) {
437 if (!iter
.isExpanded()) {
441 me
.getView().focusRow(node
);
444 selectById: function(nodeid
) {
445 let rootnode
= me
.store
.getRootNode();
447 if (nodeid
=== 'root') {
450 node
= rootnode
.findChild('id', nodeid
, true);
453 me
.selectExpand(node
);
457 applyState: function(state
) {
458 if (state
&& state
.value
) {
459 me
.selectById(state
.value
);
461 me
.getSelectionModel().deselectAll();
468 me
.getSelectionModel().on('select', (_sm
, n
) => sp
.set(stateid
, { value
: n
.data
.id
}));
470 rstore
.on("load", updateTree
);
471 rstore
.startUpdate();