add Negate (!) to search/filter #7676

Signed-off-by: si458 <simonsmith5521@gmail.com>

si458 committed Mar 11, 2026 at 13:01 UTC c5e47bf58ea5c90ef694ec018a72eda9f8b3e9e4
4 files changed +47 -34
translate/translate.json
+25 -24
@@ -31190,32 +31190,33 @@
31190 ]
31191 },
31192 {
31193 - "bs": "Filter: user:xxx ili u:xxx ip:xxx group:xxx ili g:xxx tag:xxx ili t: xxx atag:xxx ili a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31194 - "ca": "Filtre: usuari:xxx o u:xxx ip:xxx grup:xxx o g:xxx etiqueta:xxx o t: xxx atag:xxx o a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31195 - "cs": "Filtr: uživatel:xxx nebo u:xxx ip:xxx skupina:xxx nebo g:xxx tag:xxx nebo t: xxx atag:xxx nebo a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31196 - "da": "Filter: bruger:xxx eller u:xxx ip:xxx group:xxx eller g:xxx tag:xxx eller t: xxx atag:xxx eller a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: nuupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31197 - "de": "Filter: user:xxx oder u:xxx ip:xxx group:xxx oder g:xxx tag:xxx oder t: xxx atag:xxx oder a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: kein Update wsc:nofirewall wsc:beliebig connectivity:xxx c:xxx",
31198 - "en": "Filter: user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t: xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31199 - "es": "Filtro: usuario:xxx o u:xxx ip:xxx grupo:xxx o g:xxx etiqueta:xxx o t: xxx atag:xxx o a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:cualquiera connectivity:xxx c:xxx",
31200 - "fi": "Suodatin: user:xxx tai u:xxx ip:xxx group:xxx tai g:xxx tag:xxx tai t: xxx atag:xxx tai a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31201 - "fr": "Filtre : utilisateur:xxx ou u:xxx ip:xxx groupe:xxx ou g:xxx tag:xxx ou t:xxx atag:xxx ou a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31202 - "hi": "फ़िल्टर: उपयोगकर्ता: xxx या यू: xxx आईपी: xxx समूह: xxx या जी: xxx टैग: xxx या टी: xxx atag: xxx या a: xxx ओएस: xxx amt: xxx desc: xxx wsc: ठीक wsc: noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31203 - "hu": "Szűrőpő: user:xxx vagy u:xxx ip:xxx group:xxx vagy g:xxx tag:xxx vagy t: xxx atag:xxx vagy a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31204 - "it": "Filtro: utente:xxx o u:xxx ip:xxx gruppo:xxx o g:xxx tag:xxx o t: xxx atag:xxx o a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:qualsiasi connectivity:xxx c:xxx",
31205 - "ja": "フィルタ:user:xxxまたはu:xxx ip:xxx group:xxxまたはg:xxx tag:xxxまたはt:xxx atag:xxxまたはa:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31206 - "ko": "필터: 사용자:xxx 또는 u:xxx ip:xxx 그룹:xxx 또는 g:xxx 태그:xxx 또는 t: xxx atag:xxx 또는 a:xxx os:xxx amt:xxx 설명:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31207 - "nl": "Filter: user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t: xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31208 - "pl": "Filtr: użytkownik:xxx lub u:xxx ip:xxx grupa:xxx lub g:xxx tag:xxx lub t: xxx atag:xxx lub a:xxx os:xxx amt:xxx opis:xxx wsc:ok wsc:brakav wsc:brakaktualizacji wsc:brakfirewalla wsc:każdy connectivity:xxx c:xxx",
31209 - "pt": "Filtro: user:xxx ou u:xxx ip:xxx group:xxx ou g:xxx tag:xxx ou t: xxx atag:xxx ou a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31210 - "pt-br": "Filtro: usuário: xxx ou u:xxx ip:xxx grupo:xxx ou g:xxx tag:xxx ou t: xxx atag:xxx ou a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31211 - "ru": "Фильтр: user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t: xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31212 - "sv": "Filter: user:xxx eller u:xxx ip:xxx group:xxx eller g:xxx tag:xxx eller t: xxx atag:xxx eller a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: nupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31213 - "tr": "Filtre: user:xxx veya u:xxx ip:xxx group:xxx veya g:xxx etiketi:xxx veya t: xxx atag:xxx veya a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:herhangi biri connectivity:xxx c:xxx",
31214 - "uk": "Фільтр: user:xxx або u:xxx ip:xxx group:xxx або g:xxx tag:xxx або t: xxx atag:xxx або a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31215 - "zh-chs": "过滤器:user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t:xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31216 - "zh-cht": "過濾器:user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t:xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc: noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx",
31193 + "bs": "Filtrirajte uređaje po imenu ili prefiksu: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefiks sa ! za isključivanje, npr. !tag:abc",
31194 + "ca": "Filtra dispositius per nom o prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix amb ! per negar, p.ex. !tag:abc",
31195 + "cs": "Filtrovat zařízení podle jména nebo prefixu: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix ! pro negaci, např. !tag:abc",
31196 + "da": "Filtrer enheder efter navn eller præfiks: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Brug ! for at udelukke, f.eks. !tag:abc",
31197 + "de": "Geräte nach Name oder Präfix filtern: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Präfix ! zum Ausschließen, z.B. !tag:abc",
31198 + "en": "Filter devices by name or prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix with ! to negate, e.g. !tag:abc",
31199 + "es": "Filtrar dispositivos por nombre o prefijo: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefijo ! para negar, p.ej. !tag:abc",
31200 + "fi": "Suodata laitteita nimen tai etuliitteen mukaan: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Käytä ! kieltämiseen, esim. !tag:abc",
31201 + "fr": "Filtrer les appareils par nom ou préfixe : user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Préfixe ! pour exclure, ex. !tag:abc",
31202 + "hi": "नाम या प्रीफ़िक्स से डिवाइस फ़िल्टर करें: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - ! से नकारें, जैसे !tag:abc",
31203 + "hu": "Eszközök szűrése név vagy előtag alapján: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - ! előtag a kizáráshoz, pl. !tag:abc",
31204 + "it": "Filtra dispositivi per nome o prefisso: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefisso ! per negare, es. !tag:abc",
31205 + "ja": "名前またはプレフィックスでデバイスをフィルタ: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - !を付けると除外, 例: !tag:abc",
31206 + "ko": "이름 또는 접두사로 장치 필터링: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - !로 제외, 예: !tag:abc",
31207 + "nl": "Filter apparaten op naam of prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix ! om uit te sluiten, bijv. !tag:abc",
31208 + "pl": "Filtruj urządzenia po nazwie lub prefiksie: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefiks ! aby wykluczyć, np. !tag:abc",
31209 + "pt": "Filtrar dispositivos por nome ou prefixo: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefixo ! para negar, ex. !tag:abc",
31210 + "pt-br": "Filtrar dispositivos por nome ou prefixo: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefixo ! para negar, ex. !tag:abc",
31211 + "ru": "Фильтр устройств по имени или префиксу: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Префикс ! для исключения, например !tag:abc",
31212 + "sv": "Filtrera enheter efter namn eller prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix ! för att utesluta, t.ex. !tag:abc",
31213 + "tr": "Cihazları ada veya önek ile filtrele: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - ! öneki ile hariç tut, örn. !tag:abc",
31214 + "uk": "Фільтр пристроїв за іменем або префіксом: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Префікс ! для виключення, наприклад !tag:abc",
31215 + "zh-chs": "按名称或前缀筛选设备:user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - 用 ! 排除,例如 !tag:abc",
31216 + "zh-cht": "按名稱或前綴篩選裝置:user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - 用 ! 排除,例如 !tag:abc",
31217 "xloc": [
31218 "default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->devListToolbar",
31219 + "default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->kvmListToolbar",
31220 "default3.handlebars->container->column_l->p1->devListToolbarSpan->devListToolbar",
31221 "default3.handlebars->container->column_l->p1->devListToolbarSpan->kvmListToolbar"
31222 ]
views/default-mobile.handlebars
+12 -6
@@ -3104,18 +3104,22 @@
3104 function onSearchInputChanged() {
3105 var x = Q('SearchInput').value.toLowerCase().trim(); putstore('_search', Q('SearchInput').value);
3106 QS('SearchInput')['background-color'] = (x == '') ? '#FFFFFF' : '#FDFFBE';
3107 + var negate = x.startsWith('!');
3108 + if (negate) x = x.substring(1);
3109 + var rawVal = Q('SearchInput').value.trim();
3110 + if (negate) rawVal = rawVal.substring(1);
3111 var userSearch = null, ipSearch = null, groupSearch = null, tagSearch = null, agentTagSearch = null, wscSearch = null, osSearch = null, amtSearch = null;
3112 if (x.startsWith('user:'.toLowerCase())) { userSearch = x.substring('user:'.length); }
3113 else if (x.startsWith('u:'.toLowerCase())) { userSearch = x.substring('u:'.length); }
3114 else if (x.startsWith('ip:'.toLowerCase())) { ipSearch = x.substring('ip:'.length); }
3115 else if (x.startsWith('group:'.toLowerCase())) { groupSearch = x.substring('group:'.length); }
3116 else if (x.startsWith('g:'.toLowerCase())) { groupSearch = x.substring('g:'.length); }
3113 - else if (x.startsWith('tag:'.toLowerCase())) { tagSearch = Q('SearchInput').value.trim().substring('tag:'.length); }
3114 - else if (x.startsWith('t:'.toLowerCase())) { tagSearch = Q('SearchInput').value.trim().substring('t:'.length); }
3115 - else if (x.startsWith('atag:'.toLowerCase())) { agentTagSearch = Q('SearchInput').value.trim().substring('atag:'.length).toLowerCase(); }
3116 - else if (x.startsWith('a:'.toLowerCase())) { agentTagSearch = Q('SearchInput').value.trim().substring('a:'.length).toLowerCase(); }
3117 - else if (x.startsWith('os:'.toLowerCase())) { osSearch = Q('SearchInput').value.trim().substring('os:'.length).toLowerCase(); }
3118 - else if (x.startsWith('amt:'.toLowerCase())) { amtSearch = Q('SearchInput').value.trim().substring('amt:'.length).toLowerCase(); }
3117 + else if (x.startsWith('tag:'.toLowerCase())) { tagSearch = rawVal.substring('tag:'.length); }
3118 + else if (x.startsWith('t:'.toLowerCase())) { tagSearch = rawVal.substring('t:'.length); }
3119 + else if (x.startsWith('atag:'.toLowerCase())) { agentTagSearch = rawVal.substring('atag:'.length).toLowerCase(); }
3120 + else if (x.startsWith('a:'.toLowerCase())) { agentTagSearch = rawVal.substring('a:'.length).toLowerCase(); }
3121 + else if (x.startsWith('os:'.toLowerCase())) { osSearch = rawVal.substring('os:'.length).toLowerCase(); }
3122 + else if (x.startsWith('amt:'.toLowerCase())) { amtSearch = rawVal.substring('amt:'.length).toLowerCase(); }
3123 else if (x == 'wsc:ok') { wscSearch = 1; }
3124 else if (x == 'wsc:noav') { wscSearch = 2; }
3125 else if (x == 'wsc:noupdate') { wscSearch = 3; }
@@ -3197,6 +3201,8 @@
3201 } catch (ex) { for (var d in nodes) { nodes[d].v = true; } }
3202 }
3203
3204 + if (negate) { for (var d in nodes) { nodes[d].v = !nodes[d].v; } }
3205 +
3206 // Check power state
3207 var onlineOnly = Q('OnlineCheckBox').checked;
3208 if (onlineOnly) { for (var d in nodes) { if ((nodes[d].conn == null) || (nodes[d].conn == 0)) { nodes[d].v = false; } } }
views/default.handlebars
+5 -2
@@ -329,7 +329,7 @@
329 <input type=button id=SelectAllButton onclick="selectallButtonFunction();" value="Select All" />&nbsp;
330 <input type=button id=GroupActionButton disabled="disabled" value="Group Action" onclick=groupActionFunction() />&nbsp;
331 <input type=button id=ScrollToTopButton onclick="onDevicesScroll(true);" style="display:none;margin-right:4px" value="Scroll To Top" />
332 - <input id=SearchInput type=input autocomplete=off placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter: user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t: xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx"/>&nbsp;
332 + <input id=SearchInput type=input autocomplete=off placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter devices by name or prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix with ! to negate, e.g. !tag:abc"/>&nbsp;
333 <span id=SearchInputClearButton style="display:none;position:relative"><img src="images/x16.png" type="button" onclick="clearDeviceSearch()" style="position:absolute;cursor:pointer;left:-18px;top:-8px" srcset="images/x32.png 2x"/></span>
334 <select id=DevFilterSelect onchange=onOnlineCheckBox(event) title="Device Filter">
335 <option value=0>All</option>
@@ -356,7 +356,7 @@
356 </span>
357 <input type="button" onclick="onDevicesScroll(true);" value="Scroll To Top" />&nbsp;
358 <input type="button" onclick="showMultiDesktopSettings()" value="Settings" />&nbsp;
359 - <input id=KvmSearchInput type=search placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) autocomplete=off onfocus=onSearchFocus(1) onblur=onSearchFocus(0) />&nbsp;
359 + <input id=KvmSearchInput type=search placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) autocomplete=off onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter devices by name or prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix with ! to negate, e.g. !tag:abc" />&nbsp;
360 <span id=KvmSearchInputClearButton style="display:none;position:relative"><img src="images/x16.png" type="button" onclick="clearDeviceSearch()" style="position:absolute;cursor:pointer;left:-18px;top:-8px" srcset="images/x32.png 2x" /></span>
361 </td>
362 <td id=devMapToolbar class=style14 style="display:none">
@@ -6376,6 +6376,8 @@
6376 }
6377
6378 function getDevicesThatMatchFilter(x) {
6379 + var negate = x.startsWith('!');
6380 + if (negate) x = x.substring(1);
6381 var r = [];
6382 var userSearch = null, ipSearch = null, groupSearch = null, tagSearch = null, agentTagSearch = null, wscSearch = null, osSearch = null, amtSearch = null, descSearch = null, connectivitySearch = null;
6383 if (x.startsWith('user:'.toLowerCase())) { userSearch = x.substring('user:'.length); }
@@ -6504,6 +6506,7 @@
6506 } catch (ex) { for (var d in nodes) { r.push(d); } }
6507 }
6508
6509 + if (negate) { var allIds = []; for (var d in nodes) { allIds.push(d); } return allIds.filter(function(d) { return r.indexOf(d) < 0; }); }
6510 return r;
6511 }
6512
views/default3.handlebars
+5 -2
@@ -452,7 +452,7 @@
452 <input type=button class="btn btn-secondary me-1 btn-sm" id=SelectAllButton onclick="selectallButtonFunction();" value="Select All" />
453 <input type=button class="btn btn-primary me-1 btn-sm" id=GroupActionButton disabled="disabled" value="Group Action" onclick=groupActionFunction() />
454 <input type=button class="btn btn-secondary me-1 btn-sm" id=ScrollToTopButton onclick="onDevicesScroll(true);" value="Scroll To Top" />
455 - <input type="text" id=SearchInput class="form-control-sm me-1 btn-sm" placeholder="Filter" onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter: user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t: xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx">
455 + <input type="text" id=SearchInput class="form-control-sm me-1 btn-sm" placeholder="Filter" onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter devices by name or prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix with ! to negate, e.g. !tag:abc">
456 <span id=SearchInputClearButton style="display:none;position:relative">
457 <span class="fa-layers fa-fw" role="button" onclick="clearDeviceSearch()" style="position:absolute;left:-25px;top:-15px">
458 <i class="fa-solid fa-circle" style="color:var(--bs-secondary-bg)"></i>
@@ -493,7 +493,7 @@
493 </span>
494 <input type="button" class="btn btn-secondary me-1 btn-sm" onclick="onDevicesScroll(true);" value="Scroll To Top" />
495 <input type="button" class="btn btn-primary me-1 btn-sm" onclick="showMultiDesktopSettings()" value="Settings" />
496 - <input type="text" id=KvmSearchInput class="form-control-sm me-1" placeholder="Filter" onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter: user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t: xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx">
496 + <input type="text" id=KvmSearchInput class="form-control-sm me-1" placeholder="Filter" onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter devices by name or prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix with ! to negate, e.g. !tag:abc">
497 <span id=KvmSearchInputClearButton style="display:none;position:relative">
498 <span class="fa-layers fa-fw" role="button" onclick="clearDeviceSearch()" style="position:absolute;left:-25px;top:-15px">
499 <i class="fa-solid fa-circle" style="color:var(--bs-secondary-bg)"></i>
@@ -7265,6 +7265,8 @@
7265 }
7266
7267 function getDevicesThatMatchFilter(x) {
7268 + var negate = x.startsWith('!');
7269 + if (negate) x = x.substring(1);
7270 var r = [];
7271 var userSearch = null, ipSearch = null, groupSearch = null, tagSearch = null, agentTagSearch = null, wscSearch = null, osSearch = null, amtSearch = null, descSearch = null, connectivitySearch = null;
7272 if (x.startsWith('user:'.toLowerCase())) { userSearch = x.substring('user:'.length); }
@@ -7393,6 +7395,7 @@
7395 } catch (ex) { for (var d in nodes) { r.push(d); } }
7396 }
7397
7398 + if (negate) { var allIds = []; for (var d in nodes) { allIds.push(d); } return allIds.filter(function(d) { return r.indexOf(d) < 0; }); }
7399 return r;
7400 }
7401