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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
This commit is contained in:
@@ -29,6 +29,13 @@ import Pagination from './components/Pagination.jsx';
|
|||||||
|
|
||||||
const API_URL = '/api';
|
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() {
|
function CommunitiesManager() {
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -93,25 +100,26 @@ function CommunitiesManager() {
|
|||||||
const usedCommunities = new Set();
|
const usedCommunities = new Set();
|
||||||
// ASNs: fields domain/type
|
// ASNs: fields domain/type
|
||||||
for (const it of asnsPayload) {
|
for (const it of asnsPayload) {
|
||||||
const v = String(it.type ?? '').trim();
|
const v = normalizeCommunityValue(it.type);
|
||||||
if (v) usedCommunities.add(v);
|
if (v) usedCommunities.add(v);
|
||||||
}
|
}
|
||||||
// IP ranges: fields ipRange/community
|
// IP ranges: fields ipRange/community
|
||||||
for (const it of ipPayload) {
|
for (const it of ipPayload) {
|
||||||
const v = String(it.community ?? '').trim();
|
const v = normalizeCommunityValue(it.community);
|
||||||
if (v) usedCommunities.add(v);
|
if (v) usedCommunities.add(v);
|
||||||
}
|
}
|
||||||
// Domains: fields domain/community
|
// Domains: fields domain/community
|
||||||
for (const it of domPayload) {
|
for (const it of domPayload) {
|
||||||
const v = String(it.community ?? '').trim();
|
const v = normalizeCommunityValue(it.community);
|
||||||
if (v) usedCommunities.add(v);
|
if (v) usedCommunities.add(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
// put dictionary items first
|
// put dictionary items first
|
||||||
for (const d of dict) {
|
for (const d of dict) {
|
||||||
const value = String(d.value || '').trim();
|
const raw = String(d.value || '').trim();
|
||||||
if (!value) continue;
|
if (!raw) continue;
|
||||||
|
const value = normalizeCommunityValue(raw);
|
||||||
map.set(value, {
|
map.set(value, {
|
||||||
value,
|
value,
|
||||||
name: String(d.name || ''),
|
name: String(d.name || ''),
|
||||||
@@ -159,7 +167,7 @@ function CommunitiesManager() {
|
|||||||
responses.forEach((res) => {
|
responses.forEach((res) => {
|
||||||
const arr = Array.isArray(res.data) ? res.data : [];
|
const arr = Array.isArray(res.data) ? res.data : [];
|
||||||
arr.forEach((f) => {
|
arr.forEach((f) => {
|
||||||
const value = String(f?.community || '').trim();
|
const value = normalizeCommunityValue(f?.community);
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
usageMap.set(value, (usageMap.get(value) || 0) + 1);
|
usageMap.set(value, (usageMap.get(value) || 0) + 1);
|
||||||
});
|
});
|
||||||
@@ -194,10 +202,10 @@ function CommunitiesManager() {
|
|||||||
const fromTagsArray = (arr) => (arr || []).join(', ');
|
const fromTagsArray = (arr) => (arr || []).join(', ');
|
||||||
|
|
||||||
const addItem = () => {
|
const addItem = () => {
|
||||||
const value = String(newItem.value || '').trim();
|
const value = normalizeCommunityValue(newItem.value);
|
||||||
if (!value) { setError('Поле value обязательно.'); return; }
|
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 (!/^(\d+|\d+:\d+)$/.test(value)) { setError('Формат: число или N:N'); return; }
|
||||||
|
if (items.some(i => i.value === value)) { setError('Такое value уже существует.'); return; }
|
||||||
setError('');
|
setError('');
|
||||||
setItems([...items, {
|
setItems([...items, {
|
||||||
value,
|
value,
|
||||||
@@ -227,7 +235,7 @@ function CommunitiesManager() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const saveEdit = () => {
|
const saveEdit = () => {
|
||||||
const value = String(editingDraft.value || '').trim();
|
const value = normalizeCommunityValue(editingDraft.value);
|
||||||
if (!value) { setError('Поле value обязательно.'); return; }
|
if (!value) { setError('Поле value обязательно.'); return; }
|
||||||
if (!/^(\d+|\d+:\d+)$/.test(value)) { setError('Формат: число или N:N'); return; }
|
if (!/^(\d+|\d+:\d+)$/.test(value)) { setError('Формат: число или N:N'); return; }
|
||||||
if (value !== editingValue && items.some(i => i.value === value)) { setError('Такое value уже существует.'); return; }
|
if (value !== editingValue && items.some(i => i.value === value)) { setError('Такое value уже существует.'); return; }
|
||||||
@@ -272,7 +280,24 @@ function CommunitiesManager() {
|
|||||||
try {
|
try {
|
||||||
const arr = JSON.parse(text);
|
const arr = JSON.parse(text);
|
||||||
if (!Array.isArray(arr)) throw new Error('Ожидается массив');
|
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) {
|
} catch (e) {
|
||||||
setError('Неверный JSON.');
|
setError('Неверный JSON.');
|
||||||
}
|
}
|
||||||
@@ -655,13 +680,14 @@ function CommunitiesManager() {
|
|||||||
placeholder="65001:200 или 200 (без указания AS)"
|
placeholder="65001:200 или 200 (без указания AS)"
|
||||||
required
|
required
|
||||||
icon={IconHash}
|
icon={IconHash}
|
||||||
helpText="Формат: число (например, 200) или N:N (например, 65001:200)"
|
helpText="Формат: число (например, 200) или N:N (например, 65001:200). Без ASN автоматически станет 65001:XXX."
|
||||||
onValidate={(val) => {
|
onValidate={(val) => {
|
||||||
const trimmed = String(val || '').trim();
|
const trimmed = String(val || '').trim();
|
||||||
if (!trimmed) return { valid: false, message: 'Поле обязательно' };
|
if (!trimmed) return { valid: false, message: 'Поле обязательно' };
|
||||||
if (!/^(\d+|\d+:\d+)$/.test(trimmed)) return { valid: false, message: 'Формат: 200 или 65001:200' };
|
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 уже существует' };
|
const normalized = normalizeCommunityValue(trimmed);
|
||||||
return { valid: true, message: 'Корректно' };
|
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)"
|
placeholder="65001:200 или 200 (без указания AS)"
|
||||||
required
|
required
|
||||||
icon={IconHash}
|
icon={IconHash}
|
||||||
helpText="Формат: число (например, 200) или N:N (например, 65001:200)"
|
helpText="Формат: число (например, 200) или N:N (например, 65001:200). Без ASN автоматически станет 65001:XXX."
|
||||||
onValidate={(val) => {
|
onValidate={(val) => {
|
||||||
const trimmed = String(val || '').trim();
|
const trimmed = String(val || '').trim();
|
||||||
if (!trimmed) return { valid: false, message: 'Поле обязательно' };
|
if (!trimmed) return { valid: false, message: 'Поле обязательно' };
|
||||||
if (!/^(\d+|\d+:\d+)$/.test(trimmed)) return { valid: false, message: 'Формат: 200 или 65001:200' };
|
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: false, message: 'Такое value уже существует' };
|
||||||
}
|
}
|
||||||
return { valid: true, message: 'Корректно' };
|
return { valid: true, message: `Будет сохранено как ${normalized}` };
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user