diff --git a/frontend/src/FilterManager.jsx b/frontend/src/FilterManager.jsx index 5be1c88..6fd9f25 100644 --- a/frontend/src/FilterManager.jsx +++ b/frontend/src/FilterManager.jsx @@ -71,7 +71,7 @@ const countryToFlag = (code) => { }; // Компонент автокомплита для выбора Community -function CommunityAutocomplete({ label, value, onChange, communities = [], required = false, placeholder = 'Введите или выберите community...' }) { +function CommunityAutocomplete({ label, value, onChange, communities = [], required = false, placeholder = 'Введите или выберите community...', baseAS = '65001' }) { const [inputValue, setInputValue] = useState(value || ''); const [isOpen, setIsOpen] = useState(false); const [highlightedIndex, setHighlightedIndex] = useState(-1); @@ -89,9 +89,22 @@ function CommunityAutocomplete({ label, value, onChange, communities = [], requi const cValue = (c.value || '').toLowerCase(); - // Поиск по полному значению + // Нормализуем значение из справочника с учётом baseAS + // Если в справочнике просто число (например "120"), считаем его как "baseAS:120" + const normalizedCValue = cValue.includes(':') ? cValue : `${baseAS}:${cValue}`; + + // Нормализуем поисковый запрос + // Если введено просто число, считаем его как "baseAS:число" + const normalizedSearch = search.includes(':') ? search : `${baseAS}:${search}`; + + // Поиск по полному значению (с нормализацией) + if (normalizedCValue.includes(search)) return true; if (cValue.includes(search)) return true; + // Поиск нормализованного значения + if (normalizedCValue === normalizedSearch) return true; + if (normalizedCValue.includes(normalizedSearch)) return true; + // Поиск по части после двоеточия (например, "120" найдёт "65001:120") if (cValue.includes(':')) { const parts = cValue.split(':'); @@ -108,6 +121,11 @@ function CommunityAutocomplete({ label, value, onChange, communities = [], requi if (searchParts[1] && cValue.includes(searchParts[1])) return true; } + // Поиск просто по числу без AS + if (!search.includes(':') && !cValue.includes(':')) { + if (cValue.includes(search)) return true; + } + // Поиск по названию и описанию if (c.name && c.name.toLowerCase().includes(search)) return true; if (c.description && c.description.toLowerCase().includes(search)) return true; @@ -586,6 +604,8 @@ function FilterManager() { // Communities directory for autocomplete const [communities, setCommunities] = useState([]); + // Базовая AS из настроек + const [baseAS, setBaseAS] = useState('65001'); // Inventory servers (from /servers endpoint) const [inventoryServers, setInventoryServers] = useState([]); @@ -647,6 +667,17 @@ function FilterManager() { })(); useEffect(() => { + // Загружаем базовую AS из настроек + (async () => { + try { + const res = await api.get('/ui-settings'); + const data = res?.data || {}; + if (data?.baseAS) setBaseAS(String(data.baseAS)); + } catch (e) { + // мягко игнорируем, используем значение по умолчанию + } + })(); + (async () => { try { const res = await api.get(`/communities`); @@ -1815,6 +1846,7 @@ function FilterManager() { value={newFilter.community} onChange={(val) => setNewFilter({ ...newFilter, community: val })} communities={communities} + baseAS={baseAS} required /> @@ -1855,6 +1887,7 @@ function FilterManager() { value={editingFilter.community} onChange={(val) => setEditingFilter({ ...editingFilter, community: val })} communities={communities} + baseAS={baseAS} required /> diff --git a/frontend/src/components/SettingsModal.jsx b/frontend/src/components/SettingsModal.jsx index 5dab722..c4fbd0d 100644 --- a/frontend/src/components/SettingsModal.jsx +++ b/frontend/src/components/SettingsModal.jsx @@ -16,6 +16,7 @@ export default function SettingsModal({ open, onClose }) { const [etag, setEtag] = useState(''); const [dohServer, setDohServer] = useState(''); const [wsUrl, setWsUrl] = useState(''); + const [baseAS, setBaseAS] = useState('65001'); useEffect(() => { if (!open) return; @@ -29,6 +30,7 @@ export default function SettingsModal({ open, onClose }) { const data = res?.data || {}; setDohServer(String(data?.dohServer || '')); setWsUrl(String(data?.wsUpdateUrl || '')); + setBaseAS(String(data?.baseAS || '65001')); const e = res?.headers?.etag || res?.headers?.ETag || ''; setEtag(e ? String(e) : ''); } catch (e) { @@ -87,7 +89,8 @@ export default function SettingsModal({ open, onClose }) { const payload = { settings: { dohServer: String(dohServer || '').trim(), - wsUpdateUrl: String(wsUrl || '').trim() + wsUpdateUrl: String(wsUrl || '').trim(), + baseAS: String(baseAS || '65001').trim() }, etag }; @@ -172,6 +175,17 @@ export default function SettingsModal({ open, onClose }) { helpText="HTTPS URL для DNS-over-HTTPS" disabled={loading || saving} /> + +