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 [itemToDelete, setItemToDelete] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [sortField, setSortField] = useState('asn');
const [sortOrder, setSortOrder] = useState('asc');
const [filterCommunity, setFilterCommunity] = useState('');
@@ -100,11 +101,14 @@ function ASNsNewManager() {
const fetchItems = async () => {
setLoading(true);
try {
const response = await api.get(`/asns`, { params: { offset: 0, limit: 0 } });
const payload = Array.isArray(response.data?.items) ? response.data.items : (Array.isArray(response.data) ? response.data : []);
const effectiveQ = searchTerm ? searchTerm : (filterCommunity ? filterCommunity : '');
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 }));
setItems(mapped);
setOriginalItems(mapped);
setTotalItems(Number.isFinite(total) ? total : 0);
setEtag(response.headers?.etag || '');
setLastModified(response.headers?.['last-modified'] || '');
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 asnOk = isValidAsn(newItem.asn);
const communityOk = isValidCommunity(newItem.community);
@@ -327,14 +336,9 @@ function ASNsNewManager() {
? 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);
// Пагинация теперь серверная
const paginatedItems = filteredByCommunity;
const totalPages = Math.max(1, Math.ceil((totalItems || paginatedItems.length) / pageSize));
// Для фильтра - список всех уникальных community
const allCommunities = Array.from(new Set(items.map(i => i.community)));
@@ -589,7 +593,7 @@ function ASNsNewManager() {
{totalPages > 1 && (
<div className="card-footer d-flex align-items-center justify-content-between">
<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>
<ul className="pagination m-0">
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>
+18 -13
View File
@@ -45,6 +45,7 @@ function DomainsNewManager() {
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [sortField, setSortField] = useState('domain');
const [sortOrder, setSortOrder] = useState('asc');
const [filterCommunity, setFilterCommunity] = useState('');
@@ -105,10 +106,13 @@ function DomainsNewManager() {
const fetchItems = async () => {
setLoading(true);
try {
const response = await api.get(`/domains-new`, { params: { offset: 0, limit: 0 } });
const payload = Array.isArray(response.data?.items) ? response.data.items : (Array.isArray(response.data) ? response.data : []);
const effectiveQ = searchTerm ? searchTerm : (filterCommunity ? filterCommunity : '');
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);
setOriginalItems(payload);
setTotalItems(Number.isFinite(total) ? total : 0);
setEtag(response.headers?.etag || '');
setLastModified(response.headers?.['last-modified'] || '');
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 domainOk = isValidDomain(newItem.domain);
const communityOk = isValidCommunity(newItem.community);
@@ -329,19 +339,14 @@ function DomainsNewManager() {
return 0;
});
// Фильтрация по community
const filteredByCommunity = filterCommunity
// Дополнительная локальная фильтрация по community только если одновременно задан поиск (сервер фильтрует по q)
const filteredByCommunity = searchTerm && filterCommunity
? sortedItems.filter(i => i.community === filterCommunity)
: sortedItems;
// Поиск
const filteredItems = filteredByCommunity.filter(i =>
i.domain.toLowerCase().includes(searchTerm.toLowerCase())
);
// Пагинация
const totalPages = Math.ceil(filteredItems.length / pageSize);
const paginatedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
// Пагинация теперь серверная: на странице уже items
const paginatedItems = filteredByCommunity;
const totalPages = Math.max(1, Math.ceil((totalItems || paginatedItems.length) / pageSize));
// Для фильтра - список всех уникальных community
const allCommunities = Array.from(new Set(items.map(i => i.community)));
@@ -597,7 +602,7 @@ function DomainsNewManager() {
{totalPages > 1 && (
<div className="card-footer d-flex align-items-center justify-content-between">
<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>
<ul className="pagination m-0">
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>
+15 -11
View File
@@ -45,6 +45,7 @@ function IPRangesManager() {
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [sortField, setSortField] = useState('ipRange');
const [sortOrder, setSortOrder] = useState('asc');
const [filterCommunity, setFilterCommunity] = useState('');
@@ -116,10 +117,13 @@ function IPRangesManager() {
const fetchItems = async () => {
setLoading(true);
try {
const response = await api.get(`/ip-ranges`, { params: { offset: 0, limit: 0 } });
const payload = Array.isArray(response.data?.items) ? response.data.items : (Array.isArray(response.data) ? response.data : []);
const effectiveQ = searchTerm ? searchTerm : (filterCommunity ? filterCommunity : '');
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);
setOriginalItems(payload);
setTotalItems(Number.isFinite(total) ? total : 0);
setEtag(response.headers?.etag || '');
setLastModified(response.headers?.['last-modified'] || '');
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 cidrOk = isValidCidr(newItem.ipRange);
const communityOk = isValidCommunity(newItem.community);
@@ -351,14 +360,9 @@ function IPRangesManager() {
? sortedItems.filter(i => i.community === filterCommunity)
: sortedItems;
// Поиск
const filteredItems = filteredByCommunity.filter(i =>
i.ipRange.toLowerCase().includes(searchTerm.toLowerCase())
);
// Пагинация
const totalPages = Math.ceil(filteredItems.length / pageSize);
const paginatedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
// Пагинация теперь серверная
const paginatedItems = filteredByCommunity;
const totalPages = Math.max(1, Math.ceil((totalItems || paginatedItems.length) / pageSize));
// Для фильтра - список всех уникальных community
const allCommunities = Array.from(new Set(items.map(i => i.community)));
@@ -857,7 +861,7 @@ function IPRangesManager() {
{totalPages > 1 && (
<div className="card-footer d-flex align-items-center justify-content-between">
<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>
<ul className="pagination m-0">
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>