From 4efe958c2e9172f96cb8a9e510543971c0a2ea1f Mon Sep 17 00:00:00 2001 From: Denis Shatskiy Date: Fri, 11 Jul 2025 07:22:20 +0700 Subject: [PATCH] feat: Add link generator functionality in ServerManager with URL settings management and CSV export feature --- frontend/src/ServerManager.jsx | 305 ++++++++++++++++++++++++++++++++- 1 file changed, 303 insertions(+), 2 deletions(-) diff --git a/frontend/src/ServerManager.jsx b/frontend/src/ServerManager.jsx index d5377fb..517daf5 100644 --- a/frontend/src/ServerManager.jsx +++ b/frontend/src/ServerManager.jsx @@ -14,7 +14,8 @@ import { IconUpload, IconAlertTriangle, IconServer, - IconChevronDown + IconChevronDown, + IconLink } from '@tabler/icons-react'; const API_URL = '/api'; @@ -50,10 +51,37 @@ function ServerManager() { const [editModalOpen, setEditModalOpen] = useState(false); const [editModalServer, setEditModalServer] = useState(null); + // Состояние для генератора ссылок + const [linkGeneratorOpen, setLinkGeneratorOpen] = useState(false); + const [linkGeneratorServer, setLinkGeneratorServer] = useState(null); + const [urlSettings, setUrlSettings] = useState({ + baseUrl: 'https://functions.yandexcloud.net/d4eno3im0qgsr4tj5hdo', + cloudflare_gateway: 'SWE-IHOR', + bunny_gateway: 'SWE-IHOR', + fastly_gateway: 'SWE-IHOR', + telegram_gateway: '94.142.140.1', + hetzner_gateway: '94.142.140.1', + type: 'routes', + version: 'v4.rsc' + }); + useEffect(() => { fetchServers(); }, []); + // Загружаем настройки URL из localStorage + useEffect(() => { + const savedSettings = localStorage.getItem('urlSettings'); + if (savedSettings) { + try { + const parsed = JSON.parse(savedSettings); + setUrlSettings(prev => ({ ...prev, ...parsed })); + } catch (e) { + console.error('Ошибка при загрузке настроек URL:', e); + } + } + }, []); + const fetchServers = async () => { setLoading(true); try { @@ -98,6 +126,72 @@ function ServerManager() { setEditModalServer(null); }; + // Функции для генератора ссылок + const handleGenerateLink = (server) => { + setLinkGeneratorServer(server); + setLinkGeneratorOpen(true); + }; + + const handleLinkGeneratorClose = () => { + setLinkGeneratorOpen(false); + setLinkGeneratorServer(null); + }; + + const generateServerUrl = (server) => { + const params = new URLSearchParams(); + + // Добавляем все параметры из настроек + Object.entries(urlSettings).forEach(([key, value]) => { + if (key !== 'baseUrl' && value) { + params.append(key, value); + } + }); + + // Добавляем IP сервера как параметр server + params.append('server', server.ip); + + return `${urlSettings.baseUrl}?${params.toString()}`; + }; + + const copyToClipboard = async (text) => { + try { + await navigator.clipboard.writeText(text); + setSuccess('Ссылка скопирована в буфер обмена!'); + setTimeout(() => setSuccess(''), 3000); + } catch (err) { + setError('Не удалось скопировать ссылку'); + } + }; + + const quickCopyLink = async (server) => { + const url = generateServerUrl(server); + await copyToClipboard(url); + }; + + const exportAllLinks = () => { + const csvContent = [ + ['IP', 'DNS', 'Country', 'Provider', 'Tunnel', 'Generated URL'], + ...servers.map(server => [ + server.ip, + server.dns, + server.country, + server.provider, + server.tunnel, + generateServerUrl(server) + ]) + ].map(row => row.map(cell => `"${cell}"`).join(',')).join('\n'); + + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', `server_links_${new Date().toISOString().split('T')[0]}.csv`); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + const handleCancelEdit = () => { setEditingServer(null); }; @@ -340,7 +434,7 @@ function ServerManager() {
+
@@ -444,6 +547,7 @@ function ServerManager() { )} + Ссылка Действия @@ -455,6 +559,19 @@ function ServerManager() { {countryToFlag(server.country)} {server.country} {server.provider} {server.tunnel} + + + @@ -506,6 +623,17 @@ function ServerManager() { onSave={handleEditModalSave} onClose={handleEditModalClose} /> + + {/* Link Generator Modal */} + ); } @@ -633,4 +761,177 @@ function DeleteServerModal({ show, server, onDelete, onClose }) { ); } +// Модальное окно для генератора ссылок +function LinkGeneratorModal({ show, server, urlSettings, onUrlSettingsChange, onGenerateUrl, onCopyToClipboard, onClose }) { + const modalRef = useRef(null); + const [localUrlSettings, setLocalUrlSettings] = useState(urlSettings); + + // Синхронизируем локальное состояние с пропсами + useEffect(() => { + setLocalUrlSettings(urlSettings); + }, [urlSettings]); + + useEffect(() => { + if (window.Tabler && window.Tabler.Modal && modalRef.current) { + const modalInstance = window.Tabler.Modal.getOrCreateInstance(modalRef.current); + if (show) { + modalInstance.show(); + } else { + modalInstance.hide(); + } + const handler = () => onClose && onClose(); + modalRef.current.addEventListener('hide.bs.modal', handler); + return () => { + if (modalRef.current) { + modalRef.current.removeEventListener('hide.bs.modal', handler); + } + }; + } + }, [show, onClose]); + + const handleSettingChange = (key, value) => { + const updated = { ...localUrlSettings, [key]: value }; + setLocalUrlSettings(updated); + onUrlSettingsChange(updated); + }; + + const handleSaveSettings = () => { + onUrlSettingsChange(localUrlSettings); + // Сохраняем в localStorage + localStorage.setItem('urlSettings', JSON.stringify(localUrlSettings)); + }; + + const generatedUrl = server ? onGenerateUrl(server) : ''; + + return ( +
+
+
+
+
+ + Генератор ссылок для {server?.ip} +
+ +
+
+
+
+
Настройки URL
+
+ + handleSettingChange('baseUrl', e.target.value)} + placeholder="https://functions.yandexcloud.net/d4eno3im0qgsr4tj5hdo" + /> +
+
+ + handleSettingChange('cloudflare_gateway', e.target.value)} + placeholder="SWE-IHOR или IP" + /> +
+
+ + handleSettingChange('bunny_gateway', e.target.value)} + placeholder="SWE-IHOR или IP" + /> +
+
+ + handleSettingChange('fastly_gateway', e.target.value)} + placeholder="SWE-IHOR или IP" + /> +
+
+
+
+ + handleSettingChange('telegram_gateway', e.target.value)} + placeholder="IP адрес" + /> +
+
+ + handleSettingChange('hetzner_gateway', e.target.value)} + placeholder="IP адрес" + /> +
+
+ + handleSettingChange('type', e.target.value)} + placeholder="routes" + /> +
+
+ + handleSettingChange('version', e.target.value)} + placeholder="v4.rsc" + /> +
+
+
+ +
+
Сгенерированная ссылка
+
+ + +
+
+
+
+ + +
+
+
+
+ ); +} + export default ServerManager; \ No newline at end of file