]>
Commit | Line | Data |
---|---|---|
18532680 | 1 | /* |
ae8f688d TL |
2 | * This is a global search field it loads the /cluster/resources on focus and displays the |
3 | * result in a floating grid. Filtering and sorting is done in the customFilter function | |
18532680 | 4 | * |
ae8f688d | 5 | * Accepts key up/down and enter for input, and it opens to CTRL+SHIFT+F and CTRL+SPACE |
18532680 DC |
6 | */ |
7 | Ext.define('PVE.form.GlobalSearchField', { | |
8 | extend: 'Ext.form.field.Text', | |
9 | alias: 'widget.pveGlobalSearchField', | |
10 | ||
11 | emptyText: gettext('Search'), | |
12 | enableKeyEvents: true, | |
13 | selectOnFocus: true, | |
14 | padding: '0 5 0 5', | |
15 | ||
16 | grid: { | |
17 | xtype: 'gridpanel', | |
ad4a19f6 | 18 | userCls: 'proxmox-tags-full', |
18532680 DC |
19 | focusOnToFront: false, |
20 | floating: true, | |
e7ade592 | 21 | emptyText: Proxmox.Utils.noneText, |
18532680 | 22 | width: 600, |
b35f19a3 | 23 | height: 400, |
18532680 DC |
24 | scrollable: { |
25 | xtype: 'scroller', | |
26 | y: true, | |
ad4a19f6 | 27 | x: true, |
18532680 DC |
28 | }, |
29 | store: { | |
30 | model: 'PVEResources', | |
8058410f | 31 | proxy: { |
56a353b9 | 32 | type: 'proxmox', |
f6710aac TL |
33 | url: '/api2/extjs/cluster/resources', |
34 | }, | |
18532680 DC |
35 | }, |
36 | plugins: { | |
37 | ptype: 'bufferedrenderer', | |
38 | trailingBufferZone: 20, | |
f6710aac | 39 | leadingBufferZone: 20, |
18532680 DC |
40 | }, |
41 | ||
42 | hideMe: function() { | |
43 | var me = this; | |
9f0b4e04 CE |
44 | if (typeof me.ctxMenu !== 'undefined' && me.ctxMenu.isVisible()) { |
45 | return; | |
46 | } | |
18532680 DC |
47 | me.hasFocus = false; |
48 | if (!me.textfield.hasFocus) { | |
49 | me.hide(); | |
50 | } | |
51 | }, | |
52 | ||
53 | setFocus: function() { | |
54 | var me = this; | |
55 | me.hasFocus = true; | |
56 | }, | |
57 | ||
58 | listeners: { | |
59 | rowclick: function(grid, record) { | |
60 | var me = this; | |
61 | me.textfield.selectAndHide(record.id); | |
62 | }, | |
9f0b4e04 CE |
63 | itemcontextmenu: function(v, record, item, index, event) { |
64 | var me = this; | |
65 | me.ctxMenu = PVE.Utils.createCmdMenu(v, record, item, index, event); | |
66 | }, | |
ae8f688d | 67 | focusleave: 'hideMe', |
f6710aac | 68 | focusenter: 'setFocus', |
18532680 DC |
69 | }, |
70 | ||
71 | columns: [ | |
72 | { | |
73 | text: gettext('Type'), | |
74 | dataIndex: 'type', | |
75 | width: 100, | |
f6710aac | 76 | renderer: PVE.Utils.render_resource_type, |
18532680 DC |
77 | }, |
78 | { | |
79 | text: gettext('Description'), | |
80 | flex: 1, | |
f6710aac | 81 | dataIndex: 'text', |
ad4a19f6 | 82 | renderer: function(value, mD, rec) { |
731436ee | 83 | let overrides = PVE.UIOptions.tagOverrides; |
ad4a19f6 DC |
84 | let tags = PVE.Utils.renderTags(rec.data.tags, overrides); |
85 | return `${value}${tags}`; | |
86 | }, | |
18532680 DC |
87 | }, |
88 | { | |
89 | text: gettext('Node'), | |
f6710aac | 90 | dataIndex: 'node', |
18532680 DC |
91 | }, |
92 | { | |
93 | text: gettext('Pool'), | |
f6710aac TL |
94 | dataIndex: 'pool', |
95 | }, | |
96 | ], | |
18532680 DC |
97 | }, |
98 | ||
99 | customFilter: function(item) { | |
86b60bd5 | 100 | let me = this; |
18532680 | 101 | |
18532680 DC |
102 | if (me.filterVal === '') { |
103 | item.data.relevance = 0; | |
104 | return true; | |
105 | } | |
ae8f688d TL |
106 | // different types have different fields to search, e.g., a node will never have a pool |
107 | const fieldMap = { | |
108 | 'pool': ['type', 'pool', 'text'], | |
109 | 'node': ['type', 'node', 'text'], | |
110 | 'storage': ['type', 'pool', 'node', 'storage'], | |
111 | 'default': ['name', 'type', 'node', 'pool', 'vmid'], | |
112 | }; | |
ad4a19f6 DC |
113 | let fields = fieldMap[item.data.type] || fieldMap.default; |
114 | let fieldArr = fields.map(field => item.data[field]?.toString().toLowerCase()); | |
115 | if (item.data.tags) { | |
116 | let tags = item.data.tags.split(/[;, ]/); | |
117 | fieldArr.push(...tags); | |
118 | } | |
18532680 | 119 | |
ae8f688d TL |
120 | let filterWords = me.filterVal.split(/\s+/); |
121 | ||
122 | // all text is case insensitive and each split-out word is searched for separately. | |
123 | // a row gets 1 point for every partial match, and and additional point for every exact match | |
124 | let match = 0; | |
ad4a19f6 DC |
125 | for (let fieldValue of fieldArr) { |
126 | if (fieldValue === undefined || fieldValue === "") { | |
86b60bd5 TL |
127 | continue; |
128 | } | |
ae8f688d TL |
129 | for (let filterWord of filterWords) { |
130 | if (fieldValue.indexOf(filterWord) !== -1) { | |
131 | match++; // partial match | |
132 | if (fieldValue === filterWord) { | |
133 | match++; // exact match is worth more | |
18532680 DC |
134 | } |
135 | } | |
136 | } | |
137 | } | |
ae8f688d | 138 | item.data.relevance = match; // set the row's virtual 'relevance' value for ordering |
53e3ea84 | 139 | return match > 0; |
18532680 DC |
140 | }, |
141 | ||
142 | updateFilter: function(field, newValue, oldValue) { | |
86b60bd5 TL |
143 | let me = this; |
144 | // parse input and filter store, show grid | |
18532680 DC |
145 | me.grid.store.filterVal = newValue.toLowerCase().trim(); |
146 | me.grid.store.clearFilter(true); | |
147 | me.grid.store.filterBy(me.customFilter); | |
148 | me.grid.getSelectionModel().select(0); | |
149 | }, | |
150 | ||
151 | selectAndHide: function(id) { | |
152 | var me = this; | |
153 | me.tree.selectById(id); | |
154 | me.grid.hide(); | |
155 | me.setValue(''); | |
156 | me.blur(); | |
157 | }, | |
158 | ||
159 | onKey: function(field, e) { | |
160 | var me = this; | |
161 | var key = e.getKey(); | |
162 | ||
8058410f | 163 | switch (key) { |
18532680 DC |
164 | case Ext.event.Event.ENTER: |
165 | // go to first entry if there is one | |
166 | if (me.grid.store.getCount() > 0) { | |
167 | me.selectAndHide(me.grid.getSelection()[0].data.id); | |
168 | } | |
169 | break; | |
170 | case Ext.event.Event.UP: | |
171 | me.grid.getSelectionModel().selectPrevious(); | |
172 | break; | |
173 | case Ext.event.Event.DOWN: | |
174 | me.grid.getSelectionModel().selectNext(); | |
175 | break; | |
176 | case Ext.event.Event.ESC: | |
177 | me.grid.hide(); | |
178 | me.blur(); | |
179 | break; | |
180 | } | |
181 | }, | |
182 | ||
183 | loadValues: function(field) { | |
86b60bd5 | 184 | let me = this; |
18532680 DC |
185 | me.hasFocus = true; |
186 | me.grid.textfield = me; | |
187 | me.grid.store.load(); | |
188 | me.grid.showBy(me, 'tl-bl'); | |
189 | }, | |
190 | ||
191 | hideGrid: function() { | |
86b60bd5 | 192 | let me = this; |
18532680 DC |
193 | me.hasFocus = false; |
194 | if (!me.grid.hasFocus) { | |
195 | me.grid.hide(); | |
196 | } | |
197 | }, | |
198 | ||
199 | listeners: { | |
200 | change: { | |
201 | fn: 'updateFilter', | |
f6710aac | 202 | buffer: 250, |
18532680 DC |
203 | }, |
204 | specialkey: 'onKey', | |
205 | focusenter: 'loadValues', | |
206 | focusleave: { | |
207 | fn: 'hideGrid', | |
f6710aac TL |
208 | delay: 100, |
209 | }, | |
18532680 DC |
210 | }, |
211 | ||
212 | toggleFocus: function() { | |
86b60bd5 | 213 | let me = this; |
18532680 DC |
214 | if (!me.hasFocus) { |
215 | me.focus(); | |
216 | } else { | |
217 | me.blur(); | |
218 | } | |
219 | }, | |
220 | ||
221 | initComponent: function() { | |
86b60bd5 | 222 | let me = this; |
18532680 DC |
223 | |
224 | if (!me.tree) { | |
225 | throw "no tree given"; | |
226 | } | |
227 | ||
228 | me.grid = Ext.create(me.grid); | |
229 | ||
230 | me.callParent(); | |
231 | ||
86b60bd5 | 232 | // bind CTRL + SHIFT + F and CTRL + SPACE to open/close the search |
18532680 DC |
233 | me.keymap = new Ext.KeyMap({ |
234 | target: Ext.get(document), | |
235 | binding: [{ | |
8058410f | 236 | key: 'F', |
18532680 DC |
237 | ctrl: true, |
238 | shift: true, | |
239 | fn: me.toggleFocus, | |
f6710aac TL |
240 | scope: me, |
241 | }, { | |
8058410f | 242 | key: ' ', |
18532680 DC |
243 | ctrl: true, |
244 | fn: me.toggleFocus, | |
f6710aac TL |
245 | scope: me, |
246 | }], | |
18532680 DC |
247 | }); |
248 | ||
86b60bd5 | 249 | // always select first item and sort by relevance after load |
18532680 DC |
250 | me.mon(me.grid.store, 'load', function() { |
251 | me.grid.getSelectionModel().select(0); | |
252 | me.grid.store.sort({ | |
253 | property: 'relevance', | |
f6710aac | 254 | direction: 'DESC', |
18532680 DC |
255 | }); |
256 | }); | |
f6710aac | 257 | }, |
18532680 | 258 | }); |