Telegram bridge
+From 1c644e1df7ebd5fb88e92fb3f6360f86355aac04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= <78488229+viktor138irk@users.noreply.github.com> Date: Fri, 8 May 2026 17:54:21 +0900 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=20=D0=BD=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA=20Telegram=20SOCKS5=20?= =?UTF-8?q?=D0=B2=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin-panel/src/main.jsx | 237 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 227 insertions(+), 10 deletions(-) diff --git a/admin-panel/src/main.jsx b/admin-panel/src/main.jsx index 57b18a3..9b9ad50 100644 --- a/admin-panel/src/main.jsx +++ b/admin-panel/src/main.jsx @@ -3,9 +3,13 @@ import { createRoot } from 'react-dom/client'; import { Activity, Bot, + CheckCircle, Clock, + KeyRound, MessageCircle, + PlugZap, RefreshCw, + Save, Server, ShieldCheck, Users, @@ -16,14 +20,28 @@ import './styles.css'; const API_URL = import.meta.env.VITE_API_URL || 'https://api.stackworks.ru'; const REFRESH_MS = 15000; +const emptyTelegramSettings = { + botToken: '', + hasBotToken: false, + proxy: { + enabled: false, + type: 'socks5', + host: '127.0.0.1', + port: 9050, + username: '', + password: '', + hasPassword: false + } +}; + function formatDate(value) { if (!value) return '—'; - const date = new Date(value.replace(' ', 'T') + 'Z'); - if (Number.isNaN(date.getTime())) return value; + const source = value instanceof Date ? value : new Date(String(value).replace(' ', 'T') + 'Z'); + if (Number.isNaN(source.getTime())) return String(value); return new Intl.DateTimeFormat('ru-RU', { dateStyle: 'short', timeStyle: 'short' - }).format(date); + }).format(source); } function StatCard({ icon: Icon, label, value }) { @@ -42,9 +60,14 @@ function App() { const [health, setHealth] = useState(null); const [stats, setStats] = useState(null); const [messages, setMessages] = useState([]); + const [telegramSettings, setTelegramSettings] = useState(emptyTelegramSettings); const [loading, setLoading] = useState(false); + const [savingTelegram, setSavingTelegram] = useState(false); + const [testingProxy, setTestingProxy] = useState(false); const [lastUpdate, setLastUpdate] = useState(null); const [error, setError] = useState(''); + const [telegramNotice, setTelegramNotice] = useState(''); + const [proxyTestResult, setProxyTestResult] = useState(null); const healthOk = health?.ok === true; @@ -56,26 +79,56 @@ function App() { { icon: Bot, label: 'Сообщения', value: stats?.messages } ]), [stats]); + function patchTelegramSettings(path, value) { + setTelegramSettings((current) => { + if (path.startsWith('proxy.')) { + const key = path.replace('proxy.', ''); + return { + ...current, + proxy: { + ...current.proxy, + [key]: value + } + }; + } + + return { + ...current, + [path]: value + }; + }); + } + + async function loadTelegramSettings() { + const response = await fetch(API_URL + '/api/admin/telegram/settings'); + const data = await response.json(); + if (!data.ok) throw new Error(data.error || 'Telegram settings load failed'); + setTelegramSettings(data.settings || emptyTelegramSettings); + } + async function loadDashboard({ silent = false } = {}) { if (!silent) setLoading(true); setError(''); try { - const [healthResponse, statsResponse, messagesResponse] = await Promise.all([ + const [healthResponse, statsResponse, messagesResponse, telegramResponse] = await Promise.all([ fetch(API_URL + '/health'), fetch(API_URL + '/api/admin/stats'), - fetch(API_URL + '/api/admin/messages?limit=25') + fetch(API_URL + '/api/admin/messages?limit=25'), + fetch(API_URL + '/api/admin/telegram/settings') ]); - const [healthData, statsData, messagesData] = await Promise.all([ + const [healthData, statsData, messagesData, telegramData] = await Promise.all([ healthResponse.json(), statsResponse.json(), - messagesResponse.json() + messagesResponse.json(), + telegramResponse.json() ]); setHealth(healthData); setStats(statsData.stats || null); setMessages(Array.isArray(messagesData.messages) ? messagesData.messages : []); + if (telegramData.ok) setTelegramSettings(telegramData.settings || emptyTelegramSettings); setLastUpdate(new Date()); } catch (requestError) { setHealth({ ok: false, error: requestError.message }); @@ -85,6 +138,58 @@ function App() { } } + async function saveTelegramSettings(event) { + event.preventDefault(); + setSavingTelegram(true); + setTelegramNotice(''); + setProxyTestResult(null); + + try { + const response = await fetch(API_URL + '/api/admin/telegram/settings', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + botToken: telegramSettings.botToken, + proxy: { + enabled: Boolean(telegramSettings.proxy.enabled), + type: telegramSettings.proxy.type || 'socks5', + host: telegramSettings.proxy.host || '', + port: Number(telegramSettings.proxy.port || 9050), + username: telegramSettings.proxy.username || '', + password: telegramSettings.proxy.password || '' + } + }) + }); + + const data = await response.json(); + if (!data.ok) throw new Error(data.error || 'Save failed'); + setTelegramSettings(data.settings || emptyTelegramSettings); + setTelegramNotice('Настройки Telegram/SOCKS5 сохранены.'); + } catch (saveError) { + setTelegramNotice(`Ошибка сохранения: ${saveError.message}`); + } finally { + setSavingTelegram(false); + } + } + + async function testProxy() { + setTestingProxy(true); + setTelegramNotice(''); + + try { + const response = await fetch(API_URL + '/api/admin/telegram/test-proxy', { + method: 'POST' + }); + const data = await response.json(); + setProxyTestResult(data); + if (data.settings) setTelegramSettings(data.settings); + } catch (testError) { + setProxyTestResult({ ok: false, status: 'error', message: testError.message }); + } finally { + setTestingProxy(false); + } + } + useEffect(() => { loadDashboard(); const timer = setInterval(() => loadDashboard({ silent: true }), REFRESH_MS); @@ -97,13 +202,16 @@ function App() {
WSChat · operator console
Сообщения с сайта, статистика, Telegram-операторы и будущие настройки виджета в одном месте.
+Сообщения с сайта, статистика, Telegram-операторы и настройки прокси в одном месте.
Telegram bridge
+Следующий крупный блок: привязка операторов Telegram, прокси и маршрутизация ответов обратно в виджет.
+Прокси уже сохраняется в SQLite. Следующий шаг — подключить Telegraf bridge и отправлять сообщения сайта операторам.