diff --git a/frontend/src/FilterManager.jsx b/frontend/src/FilterManager.jsx index 7de291e..df589d1 100644 --- a/frontend/src/FilterManager.jsx +++ b/frontend/src/FilterManager.jsx @@ -744,6 +744,9 @@ function FilterManager() { const [selectedFilterKeys, setSelectedFilterKeys] = useState(new Set()); const [confirmState, setConfirmState] = useState({ open: false, text: '', onConfirm: null }); + // Состояние для копирования правил с сервера + const [copyFromServerModalOpen, setCopyFromServerModalOpen] = useState(false); + // Communities directory for autocomplete const [communities, setCommunities] = useState([]); // Базовая AS из настроек @@ -937,9 +940,9 @@ function FilterManager() { } }); - // Фильтруем только jumphost'ы (не exit-ноды) + // Фильтруем только jumphost'ы (исключаем exit-ноды, bgp и dns серверы) const jumphosts = allServers - .filter(s => s.type !== 'exit') + .filter(s => s.type === 'jumphost') .map(s => { const shortName = s.dns?.split('.')[0] || s.ip; // Ищем legacyId по имени сервера @@ -1252,6 +1255,47 @@ function FilterManager() { } }; + // Копирование правил с другого сервера + const handleCopyRulesFromServer = async (sourceServerId) => { + if (!selectedServer) { + setError('Сначала выберите целевой сервер.'); + return; + } + + const sourceServer = servers.find(s => s.id === sourceServerId); + if (!sourceServer) { + setError('Исходный сервер не найден.'); + return; + } + + setLoading(true); + try { + // Загружаем фильтры исходного сервера + const response = await api.get(`/server-filters/${sourceServerId}`); + const sourceFilters = Array.isArray(response.data) ? response.data : []; + + if (sourceFilters.length === 0) { + setError(`У сервера "${sourceServer.name}" нет фильтров для копирования.`); + setLoading(false); + return; + } + + // Копируем фильтры на текущий сервер + const updatedFilters = [...sourceFilters]; + await api.post(`/server-filters/${selectedServer.id}`, { filters: updatedFilters }); + + setServerFilters(updatedFilters); + setCopyFromServerModalOpen(false); + setSuccess(`Скопировано ${sourceFilters.length} фильтров с сервера "${sourceServer.name}"`); + setTimeout(() => setSuccess(''), 3000); + } catch (error) { + console.error('Error copying rules from server:', error); + setError('Не удалось скопировать фильтры.'); + } finally { + setLoading(false); + } + }; + const handleAddFilter = async () => { if (!selectedServer) { setError('Сначала выберите сервер.'); @@ -1968,6 +2012,15 @@ function FilterManager() { + + ); + })} + + + {servers.filter(s => s.id !== selectedServer?.id).length === 0 && ( +
+
+ +
+

Нет других серверов для копирования

+
+ )} + + setSuccess(''), 3000); + notify.success('Сервер успешно добавлен!'); }; const handleEdit = (server) => { @@ -339,8 +336,7 @@ function ServerManager() { })); setEditModalOpen(false); setEditModalServer(null); - setSuccess('Сервер успешно обновлён!'); - setTimeout(() => setSuccess(''), 3000); + notify.success('Сервер успешно обновлён!'); }; const handleEditModalClose = () => { @@ -359,8 +355,7 @@ function ServerManager() { setServers(servers.filter(s => s.ip !== serverToDelete.ip)); setShowDeleteModal(false); setServerToDelete(null); - setSuccess('Сервер удалён!'); - setTimeout(() => setSuccess(''), 3000); + notify.success('Сервер удалён!'); } }; @@ -374,8 +369,7 @@ function ServerManager() { setServers(servers.filter(s => !selectedServers.has(s.ip))); setSelectedServers(new Set()); setConfirmState({ open: false, text: '', onConfirm: null }); - setSuccess(`Удалено ${selectedServers.size} серверов. Не забудьте сохранить!`); - setTimeout(() => setSuccess(''), 3000); + notify.success(`Удалено ${selectedServers.size} серверов. Не забудьте сохранить!`); }, }); }; @@ -385,8 +379,7 @@ function ServerManager() { setServers(servers.map(s => selectedServers.has(s.ip) ? { ...s, tunnel: newTunnel } : s )); - setSuccess(`Туннель изменён на ${newTunnel} для ${selectedServers.size} серверов. Не забудьте сохранить!`); - setTimeout(() => setSuccess(''), 3000); + notify.success(`Туннель изменён на ${newTunnel} для ${selectedServers.size} серверов. Не забудьте сохранить!`); }; const handleBulkExport = () => { @@ -427,8 +420,7 @@ function ServerManager() { if (!p) return; setUrlSettings(prev => ({ ...prev, ...p })); localStorage.setItem('urlSettings', JSON.stringify({ ...urlSettings, ...p })); - setSuccess('Профиль применён'); - setTimeout(() => setSuccess(''), 2000); + notify.success('Профиль применён'); }; // === Clipboard === @@ -440,10 +432,9 @@ function ServerManager() { textArea.select(); try { document.execCommand('copy'); - setSuccess('Скопировано в буфер обмена!'); - setTimeout(() => setSuccess(''), 3000); + notify.success('Скопировано в буфер обмена!'); } catch (err) { - setError('Не удалось скопировать'); + notify.error('Не удалось скопировать'); } document.body.removeChild(textArea); }; @@ -452,8 +443,7 @@ function ServerManager() { if (navigator.clipboard) { try { await navigator.clipboard.writeText(text); - setSuccess('Скопировано в буфер обмена!'); - setTimeout(() => setSuccess(''), 3000); + notify.success('Скопировано в буфер обмена!'); return; } catch (err) { // fallback @@ -551,11 +541,10 @@ function ServerManager() { // Не прерываем основную операцию } - setSuccess('Изменения успешно сохранены!'); - setTimeout(() => setSuccess(''), 3000); + notify.success('Изменения успешно сохранены!'); } catch (error) { console.error('Error saving changes:', error); - setError('Не удалось сохранить изменения.'); + notify.error('Не удалось сохранить изменения.'); } finally { setLoading(false); } @@ -564,17 +553,16 @@ function ServerManager() { // === Connections === const addConnectionInternal = (draft) => { if (!draft.from || !draft.to || !draft.tunnelType || !draft.ipA || !draft.ipB) { - setError('Все поля для связи должны быть заполнены.'); + notify.error('Все поля для связи должны быть заполнены.'); return false; } if (draft.from === draft.to) { - setError('Сервер A и сервер B не могут быть одинаковыми.'); + notify.error('Сервер A и сервер B не могут быть одинаковыми.'); return false; } const next = { ...draft }; setConnections((prev) => [...prev, next]); - setSuccess('Связь успешно добавлена!'); - setTimeout(() => setSuccess(''), 3000); + notify.success('Связь успешно добавлена!'); return true; }; @@ -582,7 +570,6 @@ function ServerManager() { const ok = addConnectionInternal(newConnection); if (!ok) return; setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' }); - setError(''); }; const handleConfirmGraphConnection = () => { @@ -590,38 +577,15 @@ function ServerManager() { if (!ok) return; setGraphConnectionModalOpen(false); setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' }); - setError(''); }; const handleDeleteConnection = (index) => { setConnections(connections.filter((_, i) => i !== index)); - setSuccess('Связь удалена!'); - setTimeout(() => setSuccess(''), 3000); + notify.success('Связь удалена!'); }; return (
- {/* Уведомления */} - {error && ( -
-
- -
{error}
-
- -
- )} - - {success && ( -
-
- -
{success}
-
- -
- )} - {/* Header */}
@@ -1113,7 +1077,6 @@ function ServerManager() { onClose={() => { setGraphConnectionModalOpen(false); setNewConnection({ from: '', to: '', tunnelType: 'GRE', ipA: '', ipB: '' }); - setError(''); }} onSubmit={handleConfirmGraphConnection} title="Создать связь"