From 7bfa7be02d6a1fcf6ed83eee95e363b32e37ba81 Mon Sep 17 00:00:00 2001 From: shats Date: Tue, 10 Feb 2026 15:38:44 +0700 Subject: [PATCH] feat(MikrotikBackupsManager): implement server selection UI with search, sorting, and pagination features --- frontend/src/MikrotikBackupsManager.jsx | 239 ++++++++++++++++++------ 1 file changed, 185 insertions(+), 54 deletions(-) diff --git a/frontend/src/MikrotikBackupsManager.jsx b/frontend/src/MikrotikBackupsManager.jsx index fbcc559..4240cfa 100644 --- a/frontend/src/MikrotikBackupsManager.jsx +++ b/frontend/src/MikrotikBackupsManager.jsx @@ -6,6 +6,7 @@ import TableSkeleton from './components/TableSkeleton.jsx'; import EmptyState from './components/EmptyState.jsx'; import Tooltip from './components/Tooltip.jsx'; import ServerAutocompleteInput from './components/ServerAutocompleteInput.jsx'; +import DataTable from './components/DataTable.jsx'; import { IconRefresh, IconAlertTriangle, @@ -58,6 +59,13 @@ function MikrotikBackupsManager() { const [diffResult, setDiffResult] = useState(null); const [diffLoading, setDiffLoading] = useState(false); + // UI-состояние для таблицы выбора серверов автобэкапов + const [backupSearch, setBackupSearch] = useState(''); + const [backupSortField, setBackupSortField] = useState('ip'); + const [backupSortOrder, setBackupSortOrder] = useState('asc'); + const [backupPage, setBackupPage] = useState(1); + const backupPageSize = 10; + useEffect(() => { fetchServers(); fetchUiSettings(); @@ -91,6 +99,74 @@ function MikrotikBackupsManager() { const backupServerIdsList = useMemo(() => Array.from(backupServerIds), [backupServerIds]); + // Отфильтрованный и отсортированный список Jumphost-серверов для блока автобэкапов + const filteredBackupServers = useMemo(() => { + let list = [...jumphostInputServers]; + + if (backupSearch.trim()) { + const q = backupSearch.trim().toLowerCase(); + list = list.filter((s) => { + const fields = [ + s.id, + s.ip, + s.dns, + s.extIp, + s.internalIp, + s.country, + s.provider, + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + return fields.includes(q); + }); + } + + list.sort((a, b) => { + const field = backupSortField || 'ip'; + let av = a[field] ?? ''; + let bv = b[field] ?? ''; + if (typeof av === 'string') av = av.toLowerCase(); + if (typeof bv === 'string') bv = bv.toLowerCase(); + if (av < bv) return backupSortOrder === 'asc' ? -1 : 1; + if (av > bv) return backupSortOrder === 'asc' ? 1 : -1; + return 0; + }); + + return list; + }, [jumphostInputServers, backupSearch, backupSortField, backupSortOrder]); + + const backupTotalPages = useMemo( + () => Math.max(1, Math.ceil(filteredBackupServers.length / backupPageSize)), + [filteredBackupServers.length], + ); + + const paginatedBackupServers = useMemo(() => { + const page = Math.min(Math.max(1, backupPage), backupTotalPages); + const start = (page - 1) * backupPageSize; + return filteredBackupServers.slice(start, start + backupPageSize); + }, [filteredBackupServers, backupPage, backupTotalPages]); + + const handleBackupSort = (field) => { + setBackupSortField((prevField) => { + if (prevField === field) { + setBackupSortOrder((prevOrder) => (prevOrder === 'asc' ? 'desc' : 'asc')); + return prevField; + } + setBackupSortOrder('asc'); + return field; + }); + }; + + const handleBackupSelectAll = () => { + const allIds = filteredBackupServers.map((s) => String(s.id)); + setBackupServerIds(new Set(allIds)); + }; + + const handleBackupDeselectAll = () => { + setBackupServerIds(new Set()); + }; + const fetchServers = async () => { try { setLoadingServers(true); @@ -342,21 +418,61 @@ function MikrotikBackupsManager() { {/* Блок выбора серверов для автобэкапа */}
-
-
-
Сервера для автоматических бэкапов
-
- Отмеченные здесь Jumphost-серверы будут периодически бэкапиться планировщиком на backend. +
+
+
+
Сервера для автоматических бэкапов
+
+ Отмеченные здесь Jumphost-серверы будут периодически бэкапиться планировщиком на backend. +
+
+ Выбрано для автобэкапов:{' '} + + {backupServerIds.size} из {jumphostServers.length} + +
+
+
+
+ + + + + + + + { + setBackupSearch(e.target.value); + setBackupPage(1); + }} + /> +
+
-
{jumphostServers.length === 0 ? ( @@ -364,47 +480,62 @@ function MikrotikBackupsManager() { Нет серверов типа jumphost. Добавьте их в разделе «Серверы».
) : ( -
- - - - - - - - - - - - {jumphostServers.map((s) => { - const id = s.id || s.dns || s.ip; - const checked = id && backupServerIds.has(String(id)); - return ( - - - - - - - - ); - })} - -
СерверIP / DNSСтранаПровайдер
- id && toggleBackupServer(String(id))} - /> - {s.dns || s.ip || id} -
- {s.ip} - {s.dns && s.ip ? ' · ' : ''} - {s.dns} -
-
{s.country || '-'}{s.provider || '-'}
-
+ ( +
+
{item.dns || item.ip || item.id}
+
+ {item.ip} + {item.dns && item.ip ? ' · ' : ''} + {item.dns} +
+
+ ), + }, + { + key: 'country', + title: 'Страна', + sortable: true, + render: (value) => {value || '-'}, + }, + { + key: 'provider', + title: 'Провайдер', + sortable: true, + render: (value) => {value || '-'}, + }, + ]} + items={paginatedBackupServers} + itemKey="id" + loading={loadingServers} + sortField={backupSortField} + sortOrder={backupSortOrder} + onSort={handleBackupSort} + selectedItems={backupServerIds} + onSelectItem={(id) => id && toggleBackupServer(String(id))} + onSelectAll={handleBackupSelectAll} + onDeselectAll={handleBackupDeselectAll} + pagination={{ + currentPage: backupPage, + totalPages: backupTotalPages, + totalItems: filteredBackupServers.length, + pageSize: backupPageSize, + onPageChange: setBackupPage, + }} + emptyState={{ + title: 'Серверы не найдены', + description: + backupSearch.trim().length > 0 + ? 'По вашему запросу нет Jumphost-серверов. Измените фильтры поиска.' + : 'Нет доступных Jumphost-серверов для автобэкапа.', + }} + skeletonRows={5} + /> )}