feat: Обновить менеджеры ASNs, Domains и IPRanges для поддержки серверной пагинации и улучшенной обработки запросов с учетом фильтров и поиска; добавить состояние totalItems для отображения общего количества элементов
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m58s

This commit is contained in:
2025-08-27 13:30:43 +07:00
parent ec50fcab72
commit ce62dad844
3 changed files with 48 additions and 35 deletions
+15 -11
View File
@@ -45,6 +45,7 @@ function ASNsNewManager() {
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null); const [itemToDelete, setItemToDelete] = useState(null);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [sortField, setSortField] = useState('asn'); const [sortField, setSortField] = useState('asn');
const [sortOrder, setSortOrder] = useState('asc'); const [sortOrder, setSortOrder] = useState('asc');
const [filterCommunity, setFilterCommunity] = useState(''); const [filterCommunity, setFilterCommunity] = useState('');
@@ -100,11 +101,14 @@ function ASNsNewManager() {
const fetchItems = async () => { const fetchItems = async () => {
setLoading(true); setLoading(true);
try { try {
const response = await api.get(`/asns`, { params: { offset: 0, limit: 0 } }); const effectiveQ = searchTerm ? searchTerm : (filterCommunity ? filterCommunity : '');
const payload = Array.isArray(response.data?.items) ? response.data.items : (Array.isArray(response.data) ? response.data : []); const response = await api.get(`/asns`, { params: { q: effectiveQ, offset: Math.max(0, (currentPage - 1) * pageSize), limit: pageSize, format: 'std' } });
const payload = Array.isArray(response.data?.items) ? response.data.items : [];
const total = Number(response.data?.total ?? (Array.isArray(response.data) ? response.data.length : 0));
const mapped = payload.map(item => ({ asn: item.domain, community: item.type })); const mapped = payload.map(item => ({ asn: item.domain, community: item.type }));
setItems(mapped); setItems(mapped);
setOriginalItems(mapped); setOriginalItems(mapped);
setTotalItems(Number.isFinite(total) ? total : 0);
setEtag(response.headers?.etag || ''); setEtag(response.headers?.etag || '');
setLastModified(response.headers?.['last-modified'] || ''); setLastModified(response.headers?.['last-modified'] || '');
const lengthHeader = response.headers?.['content-length-source']; const lengthHeader = response.headers?.['content-length-source'];
@@ -118,6 +122,11 @@ function ASNsNewManager() {
} }
}; };
useEffect(() => {
fetchItems();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage, searchTerm, filterCommunity]);
const handleAddItem = () => { const handleAddItem = () => {
const asnOk = isValidAsn(newItem.asn); const asnOk = isValidAsn(newItem.asn);
const communityOk = isValidCommunity(newItem.community); const communityOk = isValidCommunity(newItem.community);
@@ -327,14 +336,9 @@ function ASNsNewManager() {
? sortedItems.filter(i => i.community === filterCommunity) ? sortedItems.filter(i => i.community === filterCommunity)
: sortedItems; : sortedItems;
// Поиск // Пагинация теперь серверная
const filteredItems = filteredByCommunity.filter(i => const paginatedItems = filteredByCommunity;
i.asn.toLowerCase().includes(searchTerm.toLowerCase()) const totalPages = Math.max(1, Math.ceil((totalItems || paginatedItems.length) / pageSize));
);
// Пагинация
const totalPages = Math.ceil(filteredItems.length / pageSize);
const paginatedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
// Для фильтра - список всех уникальных community // Для фильтра - список всех уникальных community
const allCommunities = Array.from(new Set(items.map(i => i.community))); const allCommunities = Array.from(new Set(items.map(i => i.community)));
@@ -589,7 +593,7 @@ function ASNsNewManager() {
{totalPages > 1 && ( {totalPages > 1 && (
<div className="card-footer d-flex align-items-center justify-content-between"> <div className="card-footer d-flex align-items-center justify-content-between">
<div className="text-muted"> <div className="text-muted">
Показано {((currentPage - 1) * pageSize) + 1} - {Math.min(currentPage * pageSize, filteredItems.length)} из {filteredItems.length} Показано {((currentPage - 1) * pageSize) + 1} - {((currentPage - 1) * pageSize) + paginatedItems.length} из {totalItems || paginatedItems.length}
</div> </div>
<ul className="pagination m-0"> <ul className="pagination m-0">
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}> <li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>
+18 -13
View File
@@ -45,6 +45,7 @@ function DomainsNewManager() {
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null); const [itemToDelete, setItemToDelete] = useState(null);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [sortField, setSortField] = useState('domain'); const [sortField, setSortField] = useState('domain');
const [sortOrder, setSortOrder] = useState('asc'); const [sortOrder, setSortOrder] = useState('asc');
const [filterCommunity, setFilterCommunity] = useState(''); const [filterCommunity, setFilterCommunity] = useState('');
@@ -105,10 +106,13 @@ function DomainsNewManager() {
const fetchItems = async () => { const fetchItems = async () => {
setLoading(true); setLoading(true);
try { try {
const response = await api.get(`/domains-new`, { params: { offset: 0, limit: 0 } }); const effectiveQ = searchTerm ? searchTerm : (filterCommunity ? filterCommunity : '');
const payload = Array.isArray(response.data?.items) ? response.data.items : (Array.isArray(response.data) ? response.data : []); const response = await api.get(`/domains-new`, { params: { q: effectiveQ, offset: Math.max(0, (currentPage - 1) * pageSize), limit: pageSize, format: 'std' } });
const payload = Array.isArray(response.data?.items) ? response.data.items : [];
const total = Number(response.data?.total ?? (Array.isArray(response.data) ? response.data.length : 0));
setItems(payload); setItems(payload);
setOriginalItems(payload); setOriginalItems(payload);
setTotalItems(Number.isFinite(total) ? total : 0);
setEtag(response.headers?.etag || ''); setEtag(response.headers?.etag || '');
setLastModified(response.headers?.['last-modified'] || ''); setLastModified(response.headers?.['last-modified'] || '');
const lengthHeader = response.headers?.['content-length-source']; const lengthHeader = response.headers?.['content-length-source'];
@@ -122,6 +126,12 @@ function DomainsNewManager() {
} }
}; };
// Подгрузка при изменении страницы/поиска/фильтра
useEffect(() => {
fetchItems();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage, searchTerm, filterCommunity]);
const handleAddItem = () => { const handleAddItem = () => {
const domainOk = isValidDomain(newItem.domain); const domainOk = isValidDomain(newItem.domain);
const communityOk = isValidCommunity(newItem.community); const communityOk = isValidCommunity(newItem.community);
@@ -329,19 +339,14 @@ function DomainsNewManager() {
return 0; return 0;
}); });
// Фильтрация по community // Дополнительная локальная фильтрация по community только если одновременно задан поиск (сервер фильтрует по q)
const filteredByCommunity = filterCommunity const filteredByCommunity = searchTerm && filterCommunity
? sortedItems.filter(i => i.community === filterCommunity) ? sortedItems.filter(i => i.community === filterCommunity)
: sortedItems; : sortedItems;
// Поиск // Пагинация теперь серверная: на странице уже items
const filteredItems = filteredByCommunity.filter(i => const paginatedItems = filteredByCommunity;
i.domain.toLowerCase().includes(searchTerm.toLowerCase()) const totalPages = Math.max(1, Math.ceil((totalItems || paginatedItems.length) / pageSize));
);
// Пагинация
const totalPages = Math.ceil(filteredItems.length / pageSize);
const paginatedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
// Для фильтра - список всех уникальных community // Для фильтра - список всех уникальных community
const allCommunities = Array.from(new Set(items.map(i => i.community))); const allCommunities = Array.from(new Set(items.map(i => i.community)));
@@ -597,7 +602,7 @@ function DomainsNewManager() {
{totalPages > 1 && ( {totalPages > 1 && (
<div className="card-footer d-flex align-items-center justify-content-between"> <div className="card-footer d-flex align-items-center justify-content-between">
<div className="text-muted"> <div className="text-muted">
Показано {((currentPage - 1) * pageSize) + 1} - {Math.min(currentPage * pageSize, filteredItems.length)} из {filteredItems.length} Показано {((currentPage - 1) * pageSize) + 1} - {((currentPage - 1) * pageSize) + paginatedItems.length} из {totalItems || paginatedItems.length}
</div> </div>
<ul className="pagination m-0"> <ul className="pagination m-0">
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}> <li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>
+15 -11
View File
@@ -45,6 +45,7 @@ function IPRangesManager() {
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null); const [itemToDelete, setItemToDelete] = useState(null);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [sortField, setSortField] = useState('ipRange'); const [sortField, setSortField] = useState('ipRange');
const [sortOrder, setSortOrder] = useState('asc'); const [sortOrder, setSortOrder] = useState('asc');
const [filterCommunity, setFilterCommunity] = useState(''); const [filterCommunity, setFilterCommunity] = useState('');
@@ -116,10 +117,13 @@ function IPRangesManager() {
const fetchItems = async () => { const fetchItems = async () => {
setLoading(true); setLoading(true);
try { try {
const response = await api.get(`/ip-ranges`, { params: { offset: 0, limit: 0 } }); const effectiveQ = searchTerm ? searchTerm : (filterCommunity ? filterCommunity : '');
const payload = Array.isArray(response.data?.items) ? response.data.items : (Array.isArray(response.data) ? response.data : []); const response = await api.get(`/ip-ranges`, { params: { q: effectiveQ, offset: Math.max(0, (currentPage - 1) * pageSize), limit: pageSize, format: 'std' } });
const payload = Array.isArray(response.data?.items) ? response.data.items : [];
const total = Number(response.data?.total ?? (Array.isArray(response.data) ? response.data.length : 0));
setItems(payload); setItems(payload);
setOriginalItems(payload); setOriginalItems(payload);
setTotalItems(Number.isFinite(total) ? total : 0);
setEtag(response.headers?.etag || ''); setEtag(response.headers?.etag || '');
setLastModified(response.headers?.['last-modified'] || ''); setLastModified(response.headers?.['last-modified'] || '');
const lengthHeader = response.headers?.['content-length-source']; const lengthHeader = response.headers?.['content-length-source'];
@@ -133,6 +137,11 @@ function IPRangesManager() {
} }
}; };
useEffect(() => {
fetchItems();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage, searchTerm, filterCommunity]);
const handleAddItem = () => { const handleAddItem = () => {
const cidrOk = isValidCidr(newItem.ipRange); const cidrOk = isValidCidr(newItem.ipRange);
const communityOk = isValidCommunity(newItem.community); const communityOk = isValidCommunity(newItem.community);
@@ -351,14 +360,9 @@ function IPRangesManager() {
? sortedItems.filter(i => i.community === filterCommunity) ? sortedItems.filter(i => i.community === filterCommunity)
: sortedItems; : sortedItems;
// Поиск // Пагинация теперь серверная
const filteredItems = filteredByCommunity.filter(i => const paginatedItems = filteredByCommunity;
i.ipRange.toLowerCase().includes(searchTerm.toLowerCase()) const totalPages = Math.max(1, Math.ceil((totalItems || paginatedItems.length) / pageSize));
);
// Пагинация
const totalPages = Math.ceil(filteredItems.length / pageSize);
const paginatedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
// Для фильтра - список всех уникальных community // Для фильтра - список всех уникальных community
const allCommunities = Array.from(new Set(items.map(i => i.community))); const allCommunities = Array.from(new Set(items.map(i => i.community)));
@@ -857,7 +861,7 @@ function IPRangesManager() {
{totalPages > 1 && ( {totalPages > 1 && (
<div className="card-footer d-flex align-items-center justify-content-between"> <div className="card-footer d-flex align-items-center justify-content-between">
<div className="text-muted"> <div className="text-muted">
Показано {((currentPage - 1) * pageSize) + 1} - {Math.min(currentPage * pageSize, filteredItems.length)} из {filteredItems.length} Показано {((currentPage - 1) * pageSize) + 1} - {((currentPage - 1) * pageSize) + paginatedItems.length} из {totalItems || paginatedItems.length}
</div> </div>
<ul className="pagination m-0"> <ul className="pagination m-0">
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}> <li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>