d979f93ed5
This add a context menu to the entries of the global search field instead of showing the browsers default one. This allows to search and easily manage nodes, VMs and CTs similar to the behaviour in the resource tree. Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
273 lines
5.5 KiB
JavaScript
273 lines
5.5 KiB
JavaScript
/*
|
|
* This is a global search field
|
|
* it loads the /cluster/resources on focus
|
|
* and displays the result in a floating grid
|
|
*
|
|
* it filters and sorts the objects by the algorithm in
|
|
* the customFilter function
|
|
*
|
|
* also it does accept key up/down and enter for input
|
|
* and it opens to ctrl+shift+f and ctrl+space
|
|
*/
|
|
Ext.define('PVE.form.GlobalSearchField', {
|
|
extend: 'Ext.form.field.Text',
|
|
alias: 'widget.pveGlobalSearchField',
|
|
|
|
emptyText: gettext('Search'),
|
|
enableKeyEvents: true,
|
|
selectOnFocus: true,
|
|
padding: '0 5 0 5',
|
|
|
|
grid: {
|
|
xtype: 'gridpanel',
|
|
focusOnToFront: false,
|
|
floating: true,
|
|
emptyText: Proxmox.Utils.noneText,
|
|
width: 600,
|
|
height: 400,
|
|
scrollable: {
|
|
xtype: 'scroller',
|
|
y: true,
|
|
x:false
|
|
},
|
|
store: {
|
|
model: 'PVEResources',
|
|
proxy:{
|
|
type: 'proxmox',
|
|
url: '/api2/extjs/cluster/resources'
|
|
}
|
|
},
|
|
plugins: {
|
|
ptype: 'bufferedrenderer',
|
|
trailingBufferZone: 20,
|
|
leadingBufferZone: 20
|
|
},
|
|
|
|
hideMe: function() {
|
|
var me = this;
|
|
if (typeof me.ctxMenu !== 'undefined' && me.ctxMenu.isVisible()) {
|
|
return;
|
|
}
|
|
me.hasFocus = false;
|
|
if (!me.textfield.hasFocus) {
|
|
me.hide();
|
|
}
|
|
},
|
|
|
|
setFocus: function() {
|
|
var me = this;
|
|
me.hasFocus = true;
|
|
},
|
|
|
|
listeners: {
|
|
rowclick: function(grid, record) {
|
|
var me = this;
|
|
me.textfield.selectAndHide(record.id);
|
|
},
|
|
itemcontextmenu: function(v, record, item, index, event) {
|
|
var me = this;
|
|
me.ctxMenu = PVE.Utils.createCmdMenu(v, record, item, index, event);
|
|
},
|
|
/* because of lint */
|
|
focusleave: {
|
|
fn: 'hideMe'
|
|
},
|
|
focusenter: 'setFocus'
|
|
},
|
|
|
|
columns: [
|
|
{
|
|
text: gettext('Type'),
|
|
dataIndex: 'type',
|
|
width: 100,
|
|
renderer: PVE.Utils.render_resource_type
|
|
},
|
|
{
|
|
text: gettext('Description'),
|
|
flex: 1,
|
|
dataIndex: 'text'
|
|
},
|
|
{
|
|
text: gettext('Node'),
|
|
dataIndex: 'node'
|
|
},
|
|
{
|
|
text: gettext('Pool'),
|
|
dataIndex: 'pool'
|
|
}
|
|
]
|
|
},
|
|
|
|
customFilter: function(item) {
|
|
var me = this;
|
|
var match = 0;
|
|
var fieldArr = [];
|
|
var i,j, fields;
|
|
|
|
// different types of objects have different fields to search
|
|
// for example, a node will never have a pool and vice versa
|
|
switch (item.data.type) {
|
|
case 'pool': fieldArr = ['type', 'pool', 'text']; break;
|
|
case 'node': fieldArr = ['type', 'node', 'text']; break;
|
|
case 'storage': fieldArr = ['type', 'pool', 'node', 'storage']; break;
|
|
default: fieldArr = ['name', 'type', 'node', 'pool', 'vmid'];
|
|
}
|
|
if (me.filterVal === '') {
|
|
item.data.relevance = 0;
|
|
return true;
|
|
}
|
|
|
|
// all text is case insensitive and each word is
|
|
// searched alone
|
|
// for every partial match, the row gets
|
|
// 1 match point, for every exact match
|
|
// it gets 2 points
|
|
//
|
|
// results gets sorted by points (descending)
|
|
fields = me.filterVal.split(/\s+/);
|
|
for(i = 0; i < fieldArr.length; i++) {
|
|
var v = item.data[fieldArr[i]];
|
|
if (v !== undefined) {
|
|
v = v.toString().toLowerCase();
|
|
for(j = 0; j < fields.length; j++) {
|
|
if (v.indexOf(fields[j]) !== -1) {
|
|
match++;
|
|
if(v === fields[j]) {
|
|
match++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// give the row the 'relevance' value
|
|
item.data.relevance = match;
|
|
return (match > 0);
|
|
},
|
|
|
|
updateFilter: function(field, newValue, oldValue) {
|
|
var me = this;
|
|
// parse input and filter store,
|
|
// show grid
|
|
me.grid.store.filterVal = newValue.toLowerCase().trim();
|
|
me.grid.store.clearFilter(true);
|
|
me.grid.store.filterBy(me.customFilter);
|
|
me.grid.getSelectionModel().select(0);
|
|
},
|
|
|
|
selectAndHide: function(id) {
|
|
var me = this;
|
|
me.tree.selectById(id);
|
|
me.grid.hide();
|
|
me.setValue('');
|
|
me.blur();
|
|
},
|
|
|
|
onKey: function(field, e) {
|
|
var me = this;
|
|
var key = e.getKey();
|
|
|
|
switch(key) {
|
|
case Ext.event.Event.ENTER:
|
|
// go to first entry if there is one
|
|
if (me.grid.store.getCount() > 0) {
|
|
me.selectAndHide(me.grid.getSelection()[0].data.id);
|
|
}
|
|
break;
|
|
case Ext.event.Event.UP:
|
|
me.grid.getSelectionModel().selectPrevious();
|
|
break;
|
|
case Ext.event.Event.DOWN:
|
|
me.grid.getSelectionModel().selectNext();
|
|
break;
|
|
case Ext.event.Event.ESC:
|
|
me.grid.hide();
|
|
me.blur();
|
|
break;
|
|
}
|
|
},
|
|
|
|
loadValues: function(field) {
|
|
var me = this;
|
|
var records = [];
|
|
|
|
me.hasFocus = true;
|
|
me.grid.textfield = me;
|
|
me.grid.store.load();
|
|
me.grid.showBy(me, 'tl-bl');
|
|
},
|
|
|
|
hideGrid: function() {
|
|
var me = this;
|
|
|
|
me.hasFocus = false;
|
|
if (!me.grid.hasFocus) {
|
|
me.grid.hide();
|
|
}
|
|
},
|
|
|
|
listeners: {
|
|
change: {
|
|
fn: 'updateFilter',
|
|
buffer: 250
|
|
},
|
|
specialkey: 'onKey',
|
|
focusenter: 'loadValues',
|
|
focusleave: {
|
|
fn: 'hideGrid',
|
|
delay: 100
|
|
}
|
|
},
|
|
|
|
toggleFocus: function() {
|
|
var me = this;
|
|
if (!me.hasFocus) {
|
|
me.focus();
|
|
} else {
|
|
me.blur();
|
|
}
|
|
},
|
|
|
|
initComponent: function() {
|
|
var me = this;
|
|
|
|
if (!me.tree) {
|
|
throw "no tree given";
|
|
}
|
|
|
|
me.grid = Ext.create(me.grid);
|
|
|
|
me.callParent();
|
|
|
|
/*jslint confusion: true*/
|
|
/*because shift is also a function*/
|
|
// bind ctrl+shift+f and ctrl+space
|
|
// to open/close the search
|
|
me.keymap = new Ext.KeyMap({
|
|
target: Ext.get(document),
|
|
binding: [{
|
|
key:'F',
|
|
ctrl: true,
|
|
shift: true,
|
|
fn: me.toggleFocus,
|
|
scope: me
|
|
},{
|
|
key:' ',
|
|
ctrl: true,
|
|
fn: me.toggleFocus,
|
|
scope: me
|
|
}]
|
|
});
|
|
|
|
// always select first item and
|
|
// sort by relevance after load
|
|
me.mon(me.grid.store, 'load', function() {
|
|
me.grid.getSelectionModel().select(0);
|
|
me.grid.store.sort({
|
|
property: 'relevance',
|
|
direction: 'DESC'
|
|
});
|
|
});
|
|
}
|
|
|
|
});
|