/*
- * Left Treepanel, containing all the ressources we manage in this datacenter: server nodes, server storages, VMs and Containers
+ * Left Treepanel, containing all the resources we manage in this datacenter: server nodes, server storages, VMs and Containers
*/
Ext.define('PVE.tree.ResourceTree', {
extend: 'Ext.tree.TreePanel',
alias: ['widget.pveResourceTree'],
+ userCls: 'proxmox-tags-circle',
+
statics: {
typeDefaults: {
- node: {
+ node: {
iconCls: 'fa fa-building',
- text: gettext('Nodes')
+ text: gettext('Nodes'),
},
- pool: {
+ pool: {
iconCls: 'fa fa-tags',
- text: gettext('Resource Pool')
+ text: gettext('Resource Pool'),
},
storage: {
iconCls: 'fa fa-database',
- text: gettext('Storage')
+ text: gettext('Storage'),
+ },
+ sdn: {
+ iconCls: 'fa fa-th',
+ text: gettext('SDN'),
},
qemu: {
iconCls: 'fa fa-desktop',
- text: gettext('Virtual Machine')
+ text: gettext('Virtual Machine'),
},
lxc: {
//iconCls: 'x-tree-node-lxc',
iconCls: 'fa fa-cube',
- text: gettext('LXC Container')
+ text: gettext('LXC Container'),
},
template: {
- iconCls: 'fa fa-file-o'
- }
- }
+ iconCls: 'fa fa-file-o',
+ },
+ },
},
useArrows: true,
// private
nodeSortFn: function(node1, node2) {
- var n1 = node1.data;
- var n2 = node2.data;
-
- if ((n1.groupbyid && n2.groupbyid) ||
- !(n1.groupbyid || n2.groupbyid)) {
-
- var tcmp;
-
- var v1 = n1.type;
- var v2 = n2.type;
-
- if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
- return tcmp;
+ let n1 = node1.data, n2 = node2.data;
+
+ if (!n1.groupbyid === !n2.groupbyid) {
+ // first sort (group) by type
+ if (n1.type > n2.type) {
+ return 1;
+ } else if (n1.type < n2.type) {
+ return -1;
}
-
- // numeric compare for VM IDs
- // sort templates after regular VMs
- if (v1 === 'qemu' || v1 === 'lxc') {
- if (n1.template && !n2.template) {
+ // then sort (group) by ID
+ if (n1.type === 'qemu' || n2.type === 'lxc') {
+ if (!n1.template !== !n2.template) {
+ return n1.template ? 1 : -1; // sort templates after regular VMs
+ }
+ if (n1.vmid > n2.vmid) { // prefer VMID as metric for guests
return 1;
- } else if (n2.template && !n1.template) {
+ } else if (n1.vmid < n2.vmid) {
return -1;
}
- v1 = n1.vmid;
- v2 = n2.vmid;
- if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
- return tcmp;
- }
}
-
- return n1.id > n2.id ? 1 : (n1.id < n2.id ? -1 : 0);
+ // same types but not a guest
+ return n1.id > n2.id ? 1 : n1.id < n2.id ? -1 : 0;
} else if (n1.groupbyid) {
return -1;
} else if (n2.groupbyid) {
return 1;
}
+ return 0; // should not happen
},
// private: fast binary search
findInsertIndex: function(node, child, start, end) {
- var me = this;
-
- var diff = end - start;
-
- var mid = start + (diff>>1);
+ let me = this;
+ let diff = end - start;
if (diff <= 0) {
return start;
}
+ let mid = start + (diff >> 1);
- var res = me.nodeSortFn(child, node.childNodes[mid]);
+ let res = me.nodeSortFn(child, node.childNodes[mid]);
if (res <= 0) {
return me.findInsertIndex(node, child, start, mid);
} else {
},
setIconCls: function(info) {
- var me = this;
-
- var cls = PVE.Utils.get_object_icon_class(info.type, info);
-
+ let cls = PVE.Utils.get_object_icon_class(info.type, info);
if (cls !== '') {
info.iconCls = cls;
}
},
- // add additional elements to text
- // at the moment only the usage indicator for storages
+ // add additional elements to text. Currently only the usage indicator for storages
setText: function(info) {
- var me = this;
+ let me = this;
- var status = '';
+ let status = '';
if (info.type === 'storage') {
- var maxdisk = info.maxdisk;
- var disk = info.disk;
- var usage = disk/maxdisk;
- var cls = '';
- if (usage <= 1.0 && usage >= 0.0) {
- var height = (usage*100).toFixed(0);
- var neg_height = (100-usage*100).toFixed(0);
+ let usage = info.disk / info.maxdisk;
+ if (usage >= 0.0 && usage <= 1.0) {
+ let barHeight = (usage * 100).toFixed(0);
+ let remainingHeight = (100 - barHeight).toFixed(0);
status = '<div class="usage-wrapper">';
- status += '<div class="usage-negative" style="height: ';
- status += neg_height + '%"></div>';
- status += '<div class="usage" style="height: '+ height +'%"></div>';
+ status += `<div class="usage-negative" style="height: ${remainingHeight}%"></div>`;
+ status += `<div class="usage" style="height: ${barHeight}%"></div>`;
status += '</div> ';
}
}
+ info.text += PVE.Utils.renderTags(info.tags, PVE.UIOptions.tagOverrides);
+
info.text = status + info.text;
},
+ setToolTip: function(info) {
+ if (info.type === 'pool' || info.groupbyid !== undefined) {
+ return;
+ }
+
+ let qtips = [gettext('Status') + ': ' + (info.qmpstatus || info.status)];
+ if (info.lock) {
+ qtips.push(Ext.String.format(gettext('Config locked ({0})'), info.lock));
+ }
+ if (info.hastate !== 'unmanaged') {
+ qtips.push(gettext('HA State') + ": " + info.hastate);
+ }
+
+ info.qtip = qtips.join(', ');
+ },
+
// private
addChildSorted: function(node, info) {
- var me = this;
+ let me = this;
me.setIconCls(info);
me.setText(info);
+ me.setToolTip(info);
- var defaults;
if (info.groupbyid) {
info.text = info.groupbyid;
if (info.type === 'type') {
- defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
+ let defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
if (defaults && defaults.text) {
info.text = defaults.text;
}
}
}
- var child = Ext.create('PVETree', info);
+ let child = Ext.create('PVETree', info);
- var cs = node.childNodes;
- var pos;
- if (cs) {
- pos = cs[me.findInsertIndex(node, child, 0, cs.length)];
+ if (node.childNodes) {
+ let pos = me.findInsertIndex(node, child, 0, node.childNodes.length);
+ node.insertBefore(child, node.childNodes[pos]);
+ } else {
+ node.insertBefore(child);
}
- node.insertBefore(child, pos);
-
return child;
},
// private
groupChild: function(node, info, groups, level) {
- var me = this;
+ let me = this;
- var groupby = groups[level];
- var v = info[groupby];
+ let groupBy = groups[level];
+ let v = info[groupBy];
if (v) {
- var group = node.findChild('groupbyid', v);
+ let group = node.findChild('groupbyid', v);
if (!group) {
- var groupinfo;
- if (info.type === groupby) {
+ let groupinfo;
+ if (info.type === groupBy) {
groupinfo = info;
} else {
groupinfo = {
- type: groupby,
- id : groupby + "/" + v
+ type: groupBy,
+ id: groupBy + "/" + v,
};
- if (groupby !== 'type') {
- groupinfo[groupby] = v;
+ if (groupBy !== 'type') {
+ groupinfo[groupBy] = v;
}
}
groupinfo.leaf = false;
- groupinfo.groupbyid = v;
+ groupinfo.groupbyid = v;
group = me.addChildSorted(node, groupinfo);
- // fixme: remove when EXTJS has fixed those bugs?!
- group.expand(); group.collapse();
}
- if (info.type === groupby) {
+ if (info.type === groupBy) {
return group;
}
if (group) {
return me.addChildSorted(node, info);
},
- initComponent : function() {
- var me = this;
+ initComponent: function() {
+ let me = this;
- var rstore = PVE.data.ResourceStore;
- var sp = Ext.state.Manager.getProvider();
+ let rstore = PVE.data.ResourceStore;
+ let sp = Ext.state.Manager.getProvider();
if (!me.viewFilter) {
me.viewFilter = {};
}
- var pdata = {
+ let pdata = {
dataIndex: {},
- updateCount: 0
+ updateCount: 0,
};
- var store = Ext.create('Ext.data.TreeStore', {
+ let store = Ext.create('Ext.data.TreeStore', {
model: 'PVETree',
root: {
expanded: true,
id: 'root',
text: gettext('Datacenter'),
- iconCls: 'fa fa-server'
- }
+ iconCls: 'fa fa-server',
+ },
});
- var stateid = 'rid';
+ let stateid = 'rid';
- var updateTree = function() {
- var tmp;
+ const changedFields = [
+ 'text', 'running', 'template', 'status', 'qmpstatus', 'hastate', 'lock', 'tags',
+ ];
+ let updateTree = function() {
store.suspendEvents();
- var rootnode = me.store.getRootNode();
+ let rootnode = me.store.getRootNode();
// remember selected node (and all parents)
- var sm = me.getSelectionModel();
-
- var lastsel = sm.getSelection()[0];
- var reselect = false;
- var parents = [];
- var p = lastsel;
- while (p && !!(p = p.parentNode)) {
- parents.push(p);
+ let sm = me.getSelectionModel();
+ let lastsel = sm.getSelection()[0];
+ let parents = [];
+ for (let node = lastsel; node; node = node.parentNode) {
+ parents.push(node);
}
- var index = pdata.dataIndex;
-
- var groups = me.viewFilter.groups || [];
- var filterfn = me.viewFilter.filterfn;
-
- // remove vanished or moved items
- // update in place changed items
- var key;
- for (key in index) {
- if (index.hasOwnProperty(key)) {
- var olditem = index[key];
-
- // getById() use find(), which is slow (ExtJS4 DP5)
- //var item = rstore.getById(olditem.data.id);
- var item = rstore.data.get(olditem.data.id);
-
- var changed = false;
- var moved = false;
- if (item) {
- // test if any grouping attributes changed
- // this will also catch migrated nodes
- // in server view
- var i, len;
- for (i = 0, len = groups.length; i < len; i++) {
- var attr = groups[i];
- if (item.data[attr] != olditem.data[attr]) {
- //console.log("changed " + attr);
- moved = true;
- break;
- }
- }
-
- // explicitely check for node, since
- // in some views, node is not a grouping
- // attribute
- if (!moved && item.data.node !== olditem.data.node) {
+ let groups = me.viewFilter.groups || [];
+ // explicitly check for node/template, as those are not always grouping attributes
+ let moveCheckAttrs = groups.concat(['node', 'template']);
+ let filterfn = me.viewFilter.filterfn;
+
+ let reselect = false; // for disappeared nodes
+ let index = pdata.dataIndex;
+ // remove vanished or moved items and update changed items in-place
+ for (const [key, olditem] of Object.entries(index)) {
+ // getById() use find(), which is slow (ExtJS4 DP5)
+ let item = rstore.data.get(olditem.data.id);
+
+ let changed = false, moved = false;
+ if (item) {
+ // test if any grouping attributes changed, catches migrated tree-nodes in server view too
+ for (const attr of moveCheckAttrs) {
+ if (item.data[attr] !== olditem.data[attr]) {
moved = true;
+ break;
}
+ }
- // tree item has been updated
- if ((item.data.text !== olditem.data.text) ||
- (item.data.running !== olditem.data.running) ||
- (item.data.template !== olditem.data.template) ||
- (item.data.status !== olditem.data.status) ||
- (item.data.hastate!== olditem.data.hastate)) {
- //console.log("changed node/text/running " + olditem.data.id);
+ // tree item has been updated
+ for (const field of changedFields) {
+ if (item.data[field] !== olditem.data[field]) {
changed = true;
+ break;
}
-
- // fixme: also test filterfn()?
}
+ // FIXME: also test filterfn()?
+ }
- if (changed) {
- olditem.beginEdit();
- //console.log("REM UPDATE UID: " + key + " ITEM " + item.data.running);
- var info = olditem.data;
- Ext.apply(info, item.data);
- me.setIconCls(info);
- me.setText(info);
- olditem.commit();
- }
- if ((!item || moved) && olditem.isLeaf()) {
- //console.log("REM UID: " + key + " ITEM " + olditem.data.id);
- delete index[key];
- var parentNode = olditem.parentNode;
- // when the selected item disappears,
- // we have to deselect it here, and reselect it
- // later
- if (lastsel && olditem.data.id === lastsel.data.id) {
- reselect = true;
- sm.deselect(olditem);
- }
- // since the store events are suspended, we
- // manually remove the item from the store also
- store.remove(olditem);
- parentNode.removeChild(olditem, true);
+ if (changed) {
+ olditem.beginEdit();
+ let info = olditem.data;
+ Ext.apply(info, item.data);
+ me.setIconCls(info);
+ me.setText(info);
+ me.setToolTip(info);
+ olditem.commit();
+ }
+ if ((!item || moved) && olditem.isLeaf()) {
+ delete index[key];
+ let parentNode = olditem.parentNode;
+ // a selected item moved (migration) or disappeared (destroyed), so deselect that
+ // node now and try to reselect the moved (or its parent) node later
+ if (lastsel && olditem.data.id === lastsel.data.id) {
+ reselect = true;
+ sm.deselect(olditem);
}
+ // store events are suspended, so remove the item manually
+ store.remove(olditem);
+ parentNode.removeChild(olditem, true);
}
}
- // add new items
- rstore.each(function(item) {
- var olditem = index[item.data.id];
+ rstore.each(function(item) { // add new items
+ let olditem = index[item.data.id];
if (olditem) {
return;
}
-
if (filterfn && !filterfn(item)) {
return;
}
+ let info = Ext.apply({ leaf: true }, item.data);
- //console.log("ADD UID: " + item.data.id);
-
- var info = Ext.apply({ leaf: true }, item.data);
-
- var child = me.groupChild(rootnode, info, groups, 0);
+ let child = me.groupChild(rootnode, info, groups, 0);
if (child) {
index[item.data.id] = child;
}
});
store.resumeEvents();
+ store.fireEvent('refresh', store);
- // select parent node is selection vanished
+ // select parent node if original selected node vanished
if (lastsel && !rootnode.findChild('id', lastsel.data.id, true)) {
lastsel = rootnode;
- while (!!(p = parents.shift())) {
- if (!!(tmp = rootnode.findChild('id', p.data.id, true))) {
- lastsel = tmp;
+ for (const node of parents) {
+ if (rootnode.findChild('id', node.data.id, true)) {
+ lastsel = node;
break;
}
}
// on first tree load set the selection from the stateful provider
if (!pdata.updateCount) {
- rootnode.collapse();
rootnode.expand();
me.applyState(sp.get(stateid));
}
pdata.updateCount++;
- store.fireEvent('refresh', store);
};
- var statechange = function(sp, key, value) {
+ sp.on('statechange', (_sp, key, value) => {
if (key === stateid) {
me.applyState(value);
}
- };
-
- sp.on('statechange', statechange);
+ });
Ext.apply(me, {
allowSelection: true,
store: store,
viewConfig: {
- // note: animate cause problems with applyState
- animate: false
+ animate: false, // note: animate cause problems with applyState
},
- //useArrows: true,
- //rootVisible: false,
- //title: 'Resource Tree',
listeners: {
itemcontextmenu: PVE.Utils.createCmdMenu,
destroy: function() {
rstore.un("load", updateTree);
},
- beforecellmousedown: function (tree, td, cellIndex, record, tr, rowIndex, ev) {
- var sm = me.getSelectionModel();
- // disable selection when right clicking
- // except the record is already selected
- me.allowSelection = (ev.button !== 2) || sm.isSelected(record);
+ beforecellmousedown: function(tree, td, cellIndex, record, tr, rowIndex, ev) {
+ let sm = me.getSelectionModel();
+ // disable selection when right clicking except if the record is already selected
+ me.allowSelection = ev.button !== 2 || sm.isSelected(record);
},
- beforeselect: function (tree, record, index, eopts) {
- var allow = me.allowSelection;
+ beforeselect: function(tree, record, index, eopts) {
+ let allow = me.allowSelection;
me.allowSelection = true;
return allow;
},
- itemdblclick: PVE.Utils.openTreeConsole
+ itemdblclick: PVE.Utils.openTreeConsole,
},
setViewFilter: function(view) {
me.viewFilter = view;
me.clearTree();
updateTree();
},
+ setDatacenterText: function(clustername) {
+ let rootnode = me.store.getRootNode();
+
+ let rnodeText = gettext('Datacenter');
+ if (clustername !== undefined) {
+ rnodeText += ' (' + clustername + ')';
+ }
+
+ rootnode.beginEdit();
+ rootnode.data.text = rnodeText;
+ rootnode.commit();
+ },
clearTree: function() {
pdata.updateCount = 0;
- var rootnode = me.store.getRootNode();
+ let rootnode = me.store.getRootNode();
rootnode.collapse();
rootnode.removeAll();
pdata.dataIndex = {};
me.getSelectionModel().deselectAll();
},
selectExpand: function(node) {
- var sm = me.getSelectionModel();
+ let sm = me.getSelectionModel();
if (!sm.isSelected(node)) {
sm.select(node);
- var cn = node;
- while (!!(cn = cn.parentNode)) {
- if (!cn.isExpanded()) {
- cn.expand();
+ for (let iter = node; iter; iter = iter.parentNode) {
+ if (!iter.isExpanded()) {
+ iter.expand();
}
}
me.getView().focusRow(node);
}
},
selectById: function(nodeid) {
- var rootnode = me.store.getRootNode();
- var sm = me.getSelectionModel();
- var node;
+ let rootnode = me.store.getRootNode();
+ let node;
if (nodeid === 'root') {
node = rootnode;
} else {
}
return node;
},
- applyState : function(state) {
- var sm = me.getSelectionModel();
+ applyState: function(state) {
if (state && state.value) {
me.selectById(state.value);
} else {
- sm.deselectAll();
+ me.getSelectionModel().deselectAll();
}
- }
+ },
});
me.callParent();
- var sm = me.getSelectionModel();
- sm.on('select', function(sm, n) {
- sp.set(stateid, { value: n.data.id});
- });
+ me.getSelectionModel().on('select', (_sm, n) => sp.set(stateid, { value: n.data.id }));
rstore.on("load", updateTree);
rstore.startUpdate();
- //rstore.stopUpdate();
- }
+ },
});