diff --git a/frontend/src/ASNsNewManager.jsx b/frontend/src/ASNsNewManager.jsx new file mode 100644 index 0000000..b0eec48 --- /dev/null +++ b/frontend/src/ASNsNewManager.jsx @@ -0,0 +1,429 @@ +import { useState, useEffect, useRef } from 'react'; +import axios from 'axios'; +import { + IconPlus, + IconSearch, + IconEdit, + IconTrash, + IconCheck, + IconX, + IconDatabase, + IconRefresh +} from '@tabler/icons-react'; + +const API_URL = '/api'; + +function ASNsNewManager() { + const [items, setItems] = useState([]); + const [newItem, setNewItem] = useState({ asn: '', community: '' }); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + const [editingAsn, setEditingAsn] = useState(null); + const [editingValue, setEditingValue] = useState(''); + const [loading, setLoading] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [itemToDelete, setItemToDelete] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const [sortField, setSortField] = useState('asn'); + const [sortOrder, setSortOrder] = useState('asc'); + const [filterCommunity, setFilterCommunity] = useState(''); + const editInputRef = useRef(null); + const pageSize = 10; + + useEffect(() => { + fetchItems(); + }, []); + + const fetchItems = async () => { + setLoading(true); + try { + const response = await axios.get(`${API_URL}/asns`); + setItems(response.data.map(item => ({ asn: item.domain, community: item.type }))); + setError(''); + } catch (error) { + console.error('Error fetching ASNs:', error); + setError('Не удалось загрузить список ASN. Проверьте, запущен ли бэкенд.'); + } finally { + setLoading(false); + } + }; + + const handleAddItem = () => { + if (newItem.asn.trim() === '') { + setError('Номер AS не может быть пустым.'); + return; + } + if (newItem.community.trim() === '') { + setError('Community не может быть пустым.'); + return; + } + setError(''); + setItems([...items, newItem]); + setNewItem({ asn: '', community: '' }); + }; + + const handleEdit = (item) => { + setEditingAsn(item.asn); + setEditingValue(item.community); + }; + + const handleSaveEdit = (asn) => { + const updatedItems = items.map(i => + i.asn === asn ? { ...i, community: editingValue } : i + ); + setItems(updatedItems); + setEditingAsn(null); + }; + + const handleCancelEdit = () => { + setEditingAsn(null); + }; + + const handleDeleteItem = (asnToDelete) => { + setItems(items.filter(i => i.asn !== asnToDelete)); + }; + + const confirmDelete = (item) => { + setItemToDelete(item); + setShowDeleteModal(true); + }; + + const executeDelete = () => { + if (itemToDelete) { + handleDeleteItem(itemToDelete.asn); + setShowDeleteModal(false); + setItemToDelete(null); + } + }; + + const handleSaveChanges = async () => { + setLoading(true); + try { + // API ожидает domains: [{domain, type}] + await axios.post(`${API_URL}/asns`, { domains: items.map(i => ({ domain: i.asn, type: i.community })) }); + setSuccess('Изменения успешно сохранены!'); + setTimeout(() => setSuccess(''), 3000); + } catch (error) { + console.error('Error saving changes:', error); + setError('Не удалось сохранить изменения.'); + } finally { + setLoading(false); + } + }; + + // Сортировка + const sortedItems = [...items].sort((a, b) => { + let valA = a[sortField] || ''; + let valB = b[sortField] || ''; + if (typeof valA === 'string') valA = valA.toLowerCase(); + if (typeof valB === 'string') valB = valB.toLowerCase(); + if (valA < valB) return sortOrder === 'asc' ? -1 : 1; + if (valA > valB) return sortOrder === 'asc' ? 1 : -1; + return 0; + }); + + // Фильтрация по community + const filteredByCommunity = filterCommunity + ? sortedItems.filter(i => i.community === filterCommunity) + : sortedItems; + + // Поиск + const filteredItems = filteredByCommunity.filter(i => + i.asn.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Пагинация + const totalPages = Math.ceil(filteredItems.length / pageSize); + const paginatedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize); + + // Для фильтра - список всех уникальных community + const allCommunities = Array.from(new Set(items.map(i => i.community))); + + // Сортировка по клику + const handleSort = (field) => { + if (sortField === field) { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + } else { + setSortField(field); + setSortOrder('asc'); + } + }; + + // Улучшенный инлайн-редакт + useEffect(() => { + if (editingAsn && editInputRef.current) { + editInputRef.current.focus(); + } + }, [editingAsn]); + + const handleEditKeyDown = (e, asn) => { + if (e.key === 'Enter') handleSaveEdit(asn); + if (e.key === 'Escape') handleCancelEdit(); + }; + + return ( + <> + {error && ( +
| handleSort('asn')}> + Номер AS + {sortField === 'asn' && ( + + {sortOrder === 'asc' ? '▲' : '▼'} + + )} + | +handleSort('community')}> + Community + {sortField === 'community' && ( + + {sortOrder === 'asc' ? '▲' : '▼'} + + )} + | ++ |
|---|---|---|
| {item.asn} | +{item.community} | ++ {editingAsn === item.asn ? ( + <> + setEditingValue(e.target.value)} + onKeyDown={e => handleEditKeyDown(e, item.asn)} + style={{maxWidth: 120}} + /> + + + > + ) : ( + <> + + + > + )} + | +