feat: Integrate community suggestions in ASNs, Domains, and IPRanges managers for improved user input experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m14s

This commit is contained in:
2025-08-10 17:31:13 +07:00
parent 76dee39843
commit e5f8544c2e
3 changed files with 102 additions and 0 deletions
+34
View File
@@ -35,6 +35,19 @@ function ASNsNewManager() {
const editInputRef = useRef(null);
const pageSize = 10;
// Справочник community для подсказок
const [communities, setCommunities] = useState([]);
useEffect(() => {
(async () => {
try {
const res = await axios.get(`${API_URL}/communities`);
setCommunities(Array.isArray(res.data) ? res.data : []);
} catch (e) {
// тихо игнорируем
}
})();
}, []);
const isValidAsn = (value) => /^[0-9]+$/.test(String(value).trim());
// Допускаем как числовые, так и строковые (AS:NNN) community
const isValidCommunity = (value) => /^(\d+|\d+:\d+)$/.test(String(value).trim());
@@ -262,6 +275,7 @@ function ASNsNewManager() {
<div className="mb-3">
<label className="form-label">Community</label>
<input
list="community-options"
type="text"
className={`form-control${newInvalid.community ? ' is-invalid' : ''}`}
placeholder="112"
@@ -269,6 +283,19 @@ function ASNsNewManager() {
onChange={(e) => setNewItem({ ...newItem, community: e.target.value })}
/>
{newInvalid.community && <div className="invalid-feedback">Только цифры</div>}
{(() => {
const match = communities.find(c => c.value === String(newItem.community).trim());
if (!match) return null;
return (
<div className="form-text">
{match.name && (<><strong>{match.name}</strong> </>)}
{match.description || ''}
{match.tags && match.tags.length > 0 && (
<> (<span className="text-muted">{match.tags.join(', ')}</span>)</>
)}
</div>
);
})()}
</div>
<div className="form-footer">
<button type="submit" className="btn btn-primary w-100">
@@ -401,6 +428,7 @@ function ASNsNewManager() {
{editingAsn === item.asn ? (
<>
<input
list="community-options"
type="text"
className={`form-control d-inline-block w-auto me-2${isValidCommunity(editingValue) ? '' : ' is-invalid'}`}
value={editingValue}
@@ -513,6 +541,12 @@ function ASNsNewManager() {
</div>
</div>
)}
{/* Общий список подсказок community */}
<datalist id="community-options">
{communities.map(c => (
<option key={c.value} value={c.value}>{c.name ? `${c.name}${c.value}` : c.value}</option>
))}
</datalist>
</>
);
}
+34
View File
@@ -35,6 +35,19 @@ function DomainsNewManager() {
const editInputRef = useRef(null);
const pageSize = 10;
// Справочник community для подсказок
const [communities, setCommunities] = useState([]);
useEffect(() => {
(async () => {
try {
const res = await axios.get(`${API_URL}/communities`);
setCommunities(Array.isArray(res.data) ? res.data : []);
} catch (e) {
// тихо игнорируем
}
})();
}, []);
const isValidDomain = (value) => {
const v = String(value).trim().toLowerCase();
// Простой валидатор FQDN: sub.domain.tld
@@ -268,6 +281,7 @@ function DomainsNewManager() {
<div className="mb-3">
<label className="form-label">Community</label>
<input
list="community-options"
type="text"
className={`form-control${newInvalid.community ? ' is-invalid' : ''}`}
placeholder="100"
@@ -275,6 +289,19 @@ function DomainsNewManager() {
onChange={(e) => setNewItem({ ...newItem, community: e.target.value })}
/>
{newInvalid.community && <div className="invalid-feedback">Только цифры</div>}
{(() => {
const match = communities.find(c => c.value === String(newItem.community).trim());
if (!match) return null;
return (
<div className="form-text">
{match.name && (<><strong>{match.name}</strong> </>)}
{match.description || ''}
{match.tags && match.tags.length > 0 && (
<> (<span className="text-muted">{match.tags.join(', ')}</span>)</>
)}
</div>
);
})()}
</div>
<div className="form-footer">
<button type="submit" className="btn btn-primary w-100">
@@ -407,6 +434,7 @@ function DomainsNewManager() {
{editingDomain === item.domain ? (
<>
<input
list="community-options"
type="text"
className={`form-control d-inline-block w-auto me-2${isValidCommunity(editingValue) ? '' : ' is-invalid'}`}
value={editingValue}
@@ -519,6 +547,12 @@ function DomainsNewManager() {
</div>
</div>
)}
{/* Общий список подсказок community */}
<datalist id="community-options">
{communities.map(c => (
<option key={c.value} value={c.value}>{c.name ? `${c.name}${c.value}` : c.value}</option>
))}
</datalist>
</>
);
}
+34
View File
@@ -35,6 +35,19 @@ function IPRangesManager() {
const editInputRef = useRef(null);
const pageSize = 10;
// Справочник community для подсказок
const [communities, setCommunities] = useState([]);
useEffect(() => {
(async () => {
try {
const res = await axios.get(`${API_URL}/communities`);
setCommunities(Array.isArray(res.data) ? res.data : []);
} catch (e) {
// тихо игнорируем
}
})();
}, []);
const isValidIPv4 = (ip) => {
const octets = ip.split('.');
if (octets.length !== 4) return false;
@@ -277,6 +290,7 @@ function IPRangesManager() {
<div className="mb-3">
<label className="form-label">Community</label>
<input
list="community-options"
type="text"
className={`form-control${newInvalid.community ? ' is-invalid' : ''}`}
placeholder="65001:100"
@@ -284,6 +298,19 @@ function IPRangesManager() {
onChange={(e) => setNewItem({ ...newItem, community: e.target.value })}
/>
{newInvalid.community && <div className="invalid-feedback">Только цифры</div>}
{(() => {
const match = communities.find(c => c.value === String(newItem.community).trim());
if (!match) return null;
return (
<div className="form-text">
{match.name && (<><strong>{match.name}</strong> </>)}
{match.description || ''}
{match.tags && match.tags.length > 0 && (
<> (<span className="text-muted">{match.tags.join(', ')}</span>)</>
)}
</div>
);
})()}
</div>
<div className="form-footer">
<button type="submit" className="btn btn-primary w-100">
@@ -416,6 +443,7 @@ function IPRangesManager() {
{editingIp === item.ipRange ? (
<>
<input
list="community-options"
type="text"
className={`form-control d-inline-block w-auto me-2${isValidCommunity(editingValue) ? '' : ' is-invalid'}`}
value={editingValue}
@@ -533,6 +561,12 @@ function IPRangesManager() {
</div>
</div>
)}
{/* Общий список подсказок community */}
<datalist id="community-options">
{communities.map(c => (
<option key={c.value} value={c.value}>{c.name ? `${c.name}${c.value}` : c.value}</option>
))}
</datalist>
</>
);
}