diff --git a/frontend/src/ASNsNewManager.jsx b/frontend/src/ASNsNewManager.jsx index 6acd177..421d3f2 100644 --- a/frontend/src/ASNsNewManager.jsx +++ b/frontend/src/ASNsNewManager.jsx @@ -1,14 +1,17 @@ import { useState, useEffect, useRef } from 'react'; import axios from 'axios'; -import { - IconPlus, - IconSearch, - IconEdit, - IconTrash, - IconCheck, - IconX, +import { + IconPlus, + IconSearch, + IconEdit, + IconTrash, + IconCheck, + IconX, IconDatabase, - IconRefresh + IconRefresh, + IconUpload, + IconDownload, + IconDeviceFloppy } from '@tabler/icons-react'; const API_URL = '/api'; @@ -16,6 +19,7 @@ const API_URL = '/api'; function ASNsNewManager() { const [items, setItems] = useState([]); const [newItem, setNewItem] = useState({ asn: '', community: '' }); + const [newInvalid, setNewInvalid] = useState({ asn: false, community: false }); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [searchTerm, setSearchTerm] = useState(''); @@ -31,6 +35,9 @@ function ASNsNewManager() { const editInputRef = useRef(null); const pageSize = 10; + const isValidAsn = (value) => /^[0-9]+$/.test(String(value).trim()); + const isValidCommunity = (value) => /^[0-9]+$/.test(String(value).trim()); + useEffect(() => { fetchItems(); }, []); @@ -50,17 +57,17 @@ function ASNsNewManager() { }; const handleAddItem = () => { - if (newItem.asn.trim() === '') { - setError('Номер AS не может быть пустым.'); - return; - } - if (newItem.community.trim() === '') { - setError('Community не может быть пустым.'); + const asnOk = isValidAsn(newItem.asn); + const communityOk = isValidCommunity(newItem.community); + setNewInvalid({ asn: !asnOk, community: !communityOk }); + if (!asnOk || !communityOk) { + setError('Введите корректный номер ASN и числовой community.'); return; } setError(''); - setItems([...items, newItem]); + setItems([...items, { asn: String(newItem.asn).trim(), community: String(newItem.community).trim() }]); setNewItem({ asn: '', community: '' }); + setNewInvalid({ asn: false, community: false }); }; const handleEdit = (item) => { @@ -69,8 +76,12 @@ function ASNsNewManager() { }; const handleSaveEdit = (asn) => { + if (!isValidCommunity(editingValue)) { + setError('Community должен быть числом.'); + return; + } const updatedItems = items.map(i => - i.asn === asn ? { ...i, community: editingValue } : i + i.asn === asn ? { ...i, community: String(editingValue).trim() } : i ); setItems(updatedItems); setEditingAsn(null); @@ -101,7 +112,8 @@ function ASNsNewManager() { setLoading(true); try { // API ожидает domains: [{domain, type}] - await axios.post(`${API_URL}/asns`, { domains: items.map(i => ({ domain: i.asn, type: i.community })) }); + const valid = items.filter(i => isValidAsn(i.asn) && isValidCommunity(i.community)); + await axios.post(`${API_URL}/asns`, { domains: valid.map(i => ({ domain: i.asn, type: i.community })) }); setSuccess('Изменения успешно сохранены!'); setTimeout(() => setSuccess(''), 3000); } catch (error) { @@ -112,6 +124,37 @@ function ASNsNewManager() { } }; + const handleImport = () => { + const text = window.prompt('Вставьте строки: ASN ПРОБЕЛ COMMUNITY (по одной записи на строку)'); + if (!text) return; + const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean); + const parsed = lines.map(l => { + const [a, c] = l.split(/\s+/); + return { asn: a || '', community: c || '' }; + }); + setItems(prev => [...prev, ...parsed]); + }; + + const handleExport = () => { + const header = ['asn', 'community']; + const csv = [header, ...items.map(i => [i.asn, i.community])] + .map(r => r.map(x => `"${(x ?? '').toString().replace(/"/g, '""')}"`).join(',')) + .join('\n'); + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `asns_${new Date().toISOString().split('T')[0]}.csv`; + a.click(); + URL.revokeObjectURL(url); + }; + + const clearInvalid = () => { + setItems(prev => prev.filter(i => i.asn || i.community) + .filter(i => isValidAsn(i.asn) && isValidCommunity(i.community)) + ); + }; + // Сортировка const sortedItems = [...items].sort((a, b) => { let valA = a[sortField] || ''; @@ -208,21 +251,23 @@ function ASNsNewManager() { setNewItem({ ...newItem, asn: e.target.value })} /> + {newInvalid.asn &&
Только цифры
}
setNewItem({ ...newItem, community: e.target.value })} /> + {newInvalid.community &&
Только цифры
}
+ + +
@@ -277,7 +344,7 @@ function ASNsNewManager() {
-

Список ASN

+

Список ASN {items.length}

{/* Фильтр по community */} setEditingValue(e.target.value)} onKeyDown={e => handleEditKeyDown(e, item.asn)} style={{maxWidth: 120}} /> - + ) : ( diff --git a/frontend/src/BillingManager.jsx b/frontend/src/BillingManager.jsx index b8f5eff..8de05ee 100644 --- a/frontend/src/BillingManager.jsx +++ b/frontend/src/BillingManager.jsx @@ -533,7 +533,7 @@ function BillingManager() {
- +
- {paginatedData.map(item => ( + {paginatedData.length === 0 ? ( + + + + ) : paginatedData.map(item => ( - - ))} + + ))}
Ничего не найдено. Измените фильтры или параметры поиска.
@@ -712,8 +716,8 @@ function BillingManager() {
diff --git a/frontend/src/Dashboard.jsx b/frontend/src/Dashboard.jsx index fce7cfd..7aacd1c 100644 --- a/frontend/src/Dashboard.jsx +++ b/frontend/src/Dashboard.jsx @@ -16,9 +16,9 @@ import { IconCreditCard } from '@tabler/icons-react'; -function StatCard({ icon: Icon, color, value, title, subtitle }) { +function StatCard({ icon: Icon, color, value, title, subtitle, to }) { return ( -
+
@@ -28,6 +28,9 @@ function StatCard({ icon: Icon, color, value, title, subtitle }) {
{subtitle}
+ {to && ( + + )}
); } @@ -173,6 +176,7 @@ function Dashboard() { value={loading ? '...' : stats.domainsCount ?? '—'} title="Доменов" subtitle="Всего доменов в системе" + to="/domains" />
@@ -182,6 +186,7 @@ function Dashboard() { value={loading ? '...' : stats.ipRangesCount ?? '—'} title="IP-диапазонов" subtitle="Всего IP диапазонов" + to="/ip-ranges" />
@@ -191,6 +196,7 @@ function Dashboard() { value={loading ? '...' : stats.asnsCount ?? '—'} title="AS" subtitle="Всего Autonomous Systems" + to="/asns" />
@@ -200,6 +206,7 @@ function Dashboard() { value={loading ? '...' : stats.serversCount ?? '—'} title="Серверов" subtitle="Всего серверов" + to="/servers" />
@@ -244,37 +251,7 @@ function Dashboard() {
- {/* Быстрые действия */} -
-
-

Быстрые действия

-
-
-
- - Домены - - - IP-диапазоны - - - AS - - - Серверы - - - Фильтры - - - Авто URL - - - Биллинг - -
-
-
+ {/* Убрали быстрые действия, карточки выше кликабельны */} ); } diff --git a/frontend/src/ServerManager.jsx b/frontend/src/ServerManager.jsx index 36e0be0..4f7b7e5 100644 --- a/frontend/src/ServerManager.jsx +++ b/frontend/src/ServerManager.jsx @@ -479,7 +479,7 @@ function ServerManager() { {/* Таблица серверов на всю ширину */}
-

Список серверов

+

Список серверов {filteredServers.length}

{/* Фильтры и поиск */}