diff --git a/Data/Server/WebUI/src/App.jsx b/Data/Server/WebUI/src/App.jsx
index aa56b85..5179868 100644
--- a/Data/Server/WebUI/src/App.jsx
+++ b/Data/Server/WebUI/src/App.jsx
@@ -19,6 +19,7 @@ import {
Logout as LogoutIcon,
NavigateNext as NavigateNextIcon
} from "@mui/icons-material";
+ import ClickAwayListener from "@mui/material/ClickAwayListener";
import SearchIcon from "@mui/icons-material/Search";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import ArrowDropUpIcon from "@mui/icons-material/ArrowDropUp";
@@ -126,6 +127,27 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
const searchAnchorRef = useRef(null);
const searchDebounceRef = useRef(null);
+ // Gentle highlight helper for matched substrings
+ const highlightText = useCallback((text, query) => {
+ const t = String(text ?? "");
+ const q = String(query ?? "").trim();
+ if (!q) return t;
+ try {
+ const esc = q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const re = new RegExp(`(${esc})`, "ig");
+ const parts = t.split(re);
+ return parts.map((part, i) =>
+ part.toLowerCase() === q.toLowerCase()
+ ? (
+ {part}
+ )
+ : {part}
+ );
+ } catch {
+ return t;
+ }
+ }, []);
+
// Build breadcrumb items for current view
const breadcrumbs = React.useMemo(() => {
const items = [];
@@ -227,11 +249,16 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
// Suggest fetcher with debounce
const fetchSuggestions = useCallback((field, q) => {
- const params = new URLSearchParams({ field, q, limit: "5" });
+ const query = String(q || "").trim();
+ if (query.length < 3) {
+ setSuggestions({ devices: [], sites: [], q: query, field });
+ return;
+ }
+ const params = new URLSearchParams({ field, q: query, limit: "5" });
fetch(`/api/search/suggest?${params.toString()}`)
- .then((r) => (r.ok ? r.json() : { devices: [], sites: [] }))
+ .then((r) => (r.ok ? r.json() : { devices: [], sites: [], q: query, field }))
.then((data) => setSuggestions(data))
- .catch(() => setSuggestions({ devices: [], sites: [], q, field }));
+ .catch(() => setSuggestions({ devices: [], sites: [], q: query, field }));
}, []);
useEffect(() => {
@@ -243,7 +270,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
return () => { if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current); };
}, [searchOpen, searchCategory, searchQuery, fetchSuggestions]);
- const execSearch = useCallback((field, q, navigateImmediate = true) => {
+ const execSearch = useCallback(async (field, q, navigateImmediate = true) => {
const cat = SEARCH_CATEGORIES.find((c) => c.key === field) || SEARCH_CATEGORIES[0];
if (cat.scope === "site") {
try {
@@ -264,14 +291,42 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
serial_number: 'serialNumber', // placeholder (ignored by Device_List for now)
};
const k = fieldMap[field] || 'hostname';
- try {
- const payload = (k === 'serialNumber') ? {} : { [k]: q };
- localStorage.setItem('device_list_initial_filters', JSON.stringify(payload));
- } catch {}
- if (navigateImmediate) setCurrentPage("devices");
+ const qLc = String(q || '').toLowerCase();
+ const exact = (suggestions.devices || []).find((d) => String(d.hostname || d.value || '').toLowerCase() === qLc);
+ if (exact && (exact.hostname || '').trim()) {
+ setSelectedDevice({ hostname: exact.hostname.trim() });
+ if (navigateImmediate) setCurrentPage('device_details');
+ } else if (field === 'hostname') {
+ // Probe device existence and open directly if found
+ try {
+ const resp = await fetch(`/api/device/details/${encodeURIComponent(q)}`);
+ if (resp.ok) {
+ const data = await resp.json();
+ if (data && (data.summary?.hostname || Object.keys(data).length > 0)) {
+ setSelectedDevice({ hostname: q });
+ if (navigateImmediate) setCurrentPage('device_details');
+ } else {
+ try { localStorage.setItem('device_list_initial_filters', JSON.stringify({ [k]: q })); } catch {}
+ if (navigateImmediate) setCurrentPage('devices');
+ }
+ } else {
+ try { localStorage.setItem('device_list_initial_filters', JSON.stringify({ [k]: q })); } catch {}
+ if (navigateImmediate) setCurrentPage('devices');
+ }
+ } catch {
+ try { localStorage.setItem('device_list_initial_filters', JSON.stringify({ [k]: q })); } catch {}
+ if (navigateImmediate) setCurrentPage('devices');
+ }
+ } else {
+ try {
+ const payload = (k === 'serialNumber') ? {} : { [k]: q };
+ localStorage.setItem('device_list_initial_filters', JSON.stringify(payload));
+ } catch {}
+ if (navigateImmediate) setCurrentPage("devices");
+ }
}
setSearchOpen(false);
- }, [SEARCH_CATEGORIES, setCurrentPage]);
+ }, [SEARCH_CATEGORIES, setCurrentPage, suggestions.devices]);
const handleLoginSuccess = ({ username, role }) => {
setUser(username);
@@ -728,6 +783,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
{/* Top search: category + input */}
+ setSearchOpen(false)}>
{/* Spacer to keep user menu aligned right */}