6529dbca44
Split PVE specific models, which where not moved to the general widget toolkit, in a separate folder: data/models/ Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com> Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
266 lines
5.2 KiB
JavaScript
266 lines
5.2 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;
|
|
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);
|
|
},
|
|
/* 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'
|
|
});
|
|
});
|
|
}
|
|
|
|
});
|