]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/tree/ResourceTree.js
ui: refactor ui option related methods into UIOptions
[pve-manager.git] / www / manager6 / tree / ResourceTree.js
index 78d4cbb0eff02735c8e96cd1343f8a3278e80eda..7fcdfed5dc2c36e0dcdb55c151a90384ecec0b7f 100644 (file)
@@ -1,94 +1,90 @@
 /*
- * 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 {
@@ -97,99 +93,106 @@ Ext.define('PVE.tree.ResourceTree', {
     },
 
     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) {
@@ -200,159 +203,130 @@ Ext.define('PVE.tree.ResourceTree', {
        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;
                    }
                }
@@ -363,81 +337,82 @@ Ext.define('PVE.tree.ResourceTree', {
 
            // 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 {
@@ -448,26 +423,21 @@ Ext.define('PVE.tree.ResourceTree', {
                }
                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();
-    }
+    },
 
 });