diff --git a/frontend/src/CommunitiesManager.jsx b/frontend/src/CommunitiesManager.jsx index b61ee86..f6ff6a5 100644 --- a/frontend/src/CommunitiesManager.jsx +++ b/frontend/src/CommunitiesManager.jsx @@ -29,6 +29,13 @@ import Pagination from './components/Pagination.jsx'; const API_URL = '/api'; +const normalizeCommunityValue = (value) => { + const trimmed = String(value || '').trim(); + if (!trimmed) return ''; + // Если ASN не указан, используем 65001 по умолчанию + return trimmed.includes(':') ? trimmed : `65001:${trimmed}`; +}; + function CommunitiesManager() { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); @@ -93,25 +100,26 @@ function CommunitiesManager() { const usedCommunities = new Set(); // ASNs: fields domain/type for (const it of asnsPayload) { - const v = String(it.type ?? '').trim(); + const v = normalizeCommunityValue(it.type); if (v) usedCommunities.add(v); } // IP ranges: fields ipRange/community for (const it of ipPayload) { - const v = String(it.community ?? '').trim(); + const v = normalizeCommunityValue(it.community); if (v) usedCommunities.add(v); } // Domains: fields domain/community for (const it of domPayload) { - const v = String(it.community ?? '').trim(); + const v = normalizeCommunityValue(it.community); if (v) usedCommunities.add(v); } const map = new Map(); // put dictionary items first for (const d of dict) { - const value = String(d.value || '').trim(); - if (!value) continue; + const raw = String(d.value || '').trim(); + if (!raw) continue; + const value = normalizeCommunityValue(raw); map.set(value, { value, name: String(d.name || ''), @@ -159,7 +167,7 @@ function CommunitiesManager() { responses.forEach((res) => { const arr = Array.isArray(res.data) ? res.data : []; arr.forEach((f) => { - const value = String(f?.community || '').trim(); + const value = normalizeCommunityValue(f?.community); if (!value) return; usageMap.set(value, (usageMap.get(value) || 0) + 1); }); @@ -194,10 +202,10 @@ function CommunitiesManager() { const fromTagsArray = (arr) => (arr || []).join(', '); const addItem = () => { - const value = String(newItem.value || '').trim(); + const value = normalizeCommunityValue(newItem.value); if (!value) { setError('Поле value обязательно.'); return; } - if (items.some(i => i.value === value)) { setError('Такое value уже существует.'); return; } if (!/^(\d+|\d+:\d+)$/.test(value)) { setError('Формат: число или N:N'); return; } + if (items.some(i => i.value === value)) { setError('Такое value уже существует.'); return; } setError(''); setItems([...items, { value, @@ -227,7 +235,7 @@ function CommunitiesManager() { }; const saveEdit = () => { - const value = String(editingDraft.value || '').trim(); + const value = normalizeCommunityValue(editingDraft.value); if (!value) { setError('Поле value обязательно.'); return; } if (!/^(\d+|\d+:\d+)$/.test(value)) { setError('Формат: число или N:N'); return; } if (value !== editingValue && items.some(i => i.value === value)) { setError('Такое value уже существует.'); return; } @@ -272,7 +280,24 @@ function CommunitiesManager() { try { const arr = JSON.parse(text); if (!Array.isArray(arr)) throw new Error('Ожидается массив'); - setItems(prev => [...prev, ...arr.filter(x => x && x.value)]); + setItems(prev => { + const map = new Map(prev.map(i => [i.value, i])); + arr.forEach(x => { + if (!x || !x.value) return; + const norm = normalizeCommunityValue(x.value); + if (!norm) return; + if (map.has(norm)) return; + map.set(norm, { + value: norm, + name: String(x.name || ''), + description: String(x.description || ''), + tags: Array.isArray(x.tags) ? x.tags : [], + gatewayDefault: String(x.gatewayDefault || ''), + color: String(x.color || ''), + }); + }); + return Array.from(map.values()); + }); } catch (e) { setError('Неверный JSON.'); } @@ -655,13 +680,14 @@ function CommunitiesManager() { placeholder="65001:200 или 200 (без указания AS)" required icon={IconHash} - helpText="Формат: число (например, 200) или N:N (например, 65001:200)" + helpText="Формат: число (например, 200) или N:N (например, 65001:200). Без ASN автоматически станет 65001:XXX." onValidate={(val) => { const trimmed = String(val || '').trim(); if (!trimmed) return { valid: false, message: 'Поле обязательно' }; if (!/^(\d+|\d+:\d+)$/.test(trimmed)) return { valid: false, message: 'Формат: 200 или 65001:200' }; - if (items.some(i => i.value === trimmed)) return { valid: false, message: 'Такое value уже существует' }; - return { valid: true, message: 'Корректно' }; + const normalized = normalizeCommunityValue(trimmed); + if (items.some(i => i.value === normalized)) return { valid: false, message: 'Такое value уже существует' }; + return { valid: true, message: `Будет сохранено как ${normalized}` }; }} /> @@ -734,15 +760,16 @@ function CommunitiesManager() { placeholder="65001:200 или 200 (без указания AS)" required icon={IconHash} - helpText="Формат: число (например, 200) или N:N (например, 65001:200)" + helpText="Формат: число (например, 200) или N:N (например, 65001:200). Без ASN автоматически станет 65001:XXX." onValidate={(val) => { const trimmed = String(val || '').trim(); if (!trimmed) return { valid: false, message: 'Поле обязательно' }; if (!/^(\d+|\d+:\d+)$/.test(trimmed)) return { valid: false, message: 'Формат: 200 или 65001:200' }; - if (trimmed !== editingValue && items.some(i => i.value === trimmed)) { + const normalized = normalizeCommunityValue(trimmed); + if (normalized !== editingValue && items.some(i => i.value === normalized)) { return { valid: false, message: 'Такое value уже существует' }; } - return { valid: true, message: 'Корректно' }; + return { valid: true, message: `Будет сохранено как ${normalized}` }; }} />