feat: Introduce normalization function for community values in CommunitiesManager to ensure consistent formatting and improve input validation across various fields.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s

This commit is contained in:
2025-12-06 19:27:55 +07:00
parent 64f83a239f
commit cba1615cdf
+43 -16
View File
@@ -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}` };
}}
/>