Files
alpine-router/public/proxy.html
2026-04-13 12:40:49 +03:00

542 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AlpineRouter — Прокси</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="header-left">
<svg class="logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</svg>
<h1>AlpineRouter</h1>
</div>
<div class="header-right">
<span id="statusBadge" class="svc-badge stopped">Остановлен</span>
</div>
</header>
<nav class="tab-nav">
<a href="/" class="tab-link">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</svg>
Интерфейсы
</a>
<a href="/dhcp.html" class="tab-link">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
DHCP сервер
</a>
<a href="/clients.html" class="tab-link">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15">
<circle cx="9" cy="7" r="4"/><path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75M21 21v-2a4 4 0 0 0-3-3.87"/>
</svg>
Клиенты
</a>
<a href="/firewall.html" class="tab-link">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
<path d="M9 12l2 2 4-4"/>
</svg>
Файрвол
</a>
<a href="/proxy.html" class="tab-link active">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15">
<circle cx="12" cy="12" r="3"/>
<path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/>
</svg>
Прокси
</a>
</nav>
<main class="proxy-main">
<div id="loading" class="loading">
<div class="spinner"></div>
<span>Загрузка...</span>
</div>
<!-- Status Bar -->
<div id="statusBar" class="dhcp-status-bar hidden">
<div class="status-info">
<span class="status-label">Mihomo</span>
<span id="statusText" class="svc-badge stopped">Остановлен</span>
</div>
<div class="status-actions">
<button class="btn btn-success btn-sm" id="startBtn">Запустить</button>
<button class="btn btn-danger btn-sm" id="stopBtn" disabled>Остановить</button>
<button class="btn btn-ghost btn-sm" id="restartBtn" disabled>Перезапуск</button>
</div>
</div>
<!-- Core Info -->
<div id="coreInfo" class="alert alert-error hidden" style="margin-top:16px">
<span id="coreInfoMsg">Ядро Mihomo не найдено. Загрузите бинарный файл в раздел «Ядро».</span>
</div>
<!-- Tabs inside proxy page -->
<div class="proxy-tabs">
<button class="ptab active" data-tab="proxies">Прокси</button>
<button class="ptab" data-tab="groups">Группы</button>
<button class="ptab" data-tab="rules">Правила</button>
<button class="ptab" data-tab="settings">Настройки</button>
<button class="ptab" data-tab="logs">Логи</button>
<button class="ptab" data-tab="core">Ядро</button>
</div>
<!-- Proxies Tab -->
<div id="tab-proxies" class="ptab-content">
<div class="section-header" style="margin-bottom:16px">
<div style="display:flex;align-items:center;justify-content:space-between">
<div>
<h2>Прокси-ноды</h2>
<div class="section-desc">Добавьте прокси-серверы для маршрутизации трафика</div>
</div>
<button class="btn btn-primary btn-sm" id="addProxyBtn">+ Добавить</button>
</div>
</div>
<div id="proxyList" class="proxy-list"></div>
<div id="proxyEmpty" class="empty-state hidden">Нет прокси-нод. Нажмите «Добавить» для создания.</div>
</div>
<!-- Groups Tab -->
<div id="tab-groups" class="ptab-content hidden">
<div class="section-header" style="margin-bottom:16px">
<div style="display:flex;align-items:center;justify-content:space-between">
<div>
<h2>Группы прокси</h2>
<div class="section-desc">Балансировщики, селекторы и URL-тесты</div>
</div>
<button class="btn btn-primary btn-sm" id="addGroupBtn">+ Добавить группу</button>
</div>
</div>
<div id="groupList" class="proxy-list"></div>
<div id="groupEmpty" class="empty-state hidden">Нет групп. Нажмите «Добавить группу» для создания.</div>
</div>
<!-- Rules Tab -->
<div id="tab-rules" class="ptab-content hidden">
<div class="section-header" style="margin-bottom:16px">
<h2>Правила маршрутизации</h2>
<div class="section-desc">Определите, какой трафик куда направляется. Правила apply сверху вниз.</div>
</div>
<div class="form-row" style="margin-bottom:12px">
<button class="btn btn-primary btn-sm" id="addRuleBtn">+ Добавить правило</button>
<button class="btn btn-ghost btn-sm" id="addBlockBtn" style="margin-left:8px">+ Блокировка домена</button>
</div>
<div id="rulesList" class="rules-list"></div>
</div>
<!-- Settings Tab -->
<div id="tab-settings" class="ptab-content hidden">
<div class="section-header" style="margin-bottom:16px">
<h2>Настройки Mihomo</h2>
</div>
<form id="settingsForm" class="proxy-form">
<div class="form-row">
<label>Режим</label>
<div class="segmented" id="modeSwitch">
<button type="button" class="seg-btn active" data-mode="rule">Правила</button>
<button type="button" class="seg-btn" data-mode="global">Глобальный</button>
<button type="button" class="seg-btn" data-mode="direct">Прямой</button>
</div>
</div>
<div class="form-row">
<label for="mixedPort">Mixed Port (HTTP+SOCKS)</label>
<input type="number" id="mixedPort" value="7890">
</div>
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="allowLan" checked>
<span>Разрешить LAN</span>
</label>
</div>
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="ipv6" checked>
<span>IPv6</span>
</label>
</div>
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="tcpConcurrent" checked>
<span>TCP Concurrency</span>
</label>
</div>
<div class="form-divider"></div>
<h3 style="color:var(--text);font-size:.95rem;margin-bottom:12px">TProxy (прозрачный прокси)</h3>
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="tproxyEnabled">
<span>Включить TProxy</span>
</label>
</div>
<div class="form-row">
<label for="tproxyPort">TProxy порт</label>
<input type="number" id="tproxyPort" value="7894">
</div>
<div class="form-divider"></div>
<h3 style="color:var(--text);font-size:.95rem;margin-bottom:12px">DNS</h3>
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="dnsEnabled" checked>
<span>DNS сервер</span>
</label>
</div>
<div class="form-row">
<label for="dnsListen">DNS адрес</label>
<input type="text" id="dnsListen" value="0.0.0.0:1053">
</div>
<div class="form-row">
<label for="dnsMode">DNS режим</label>
<div class="segmented" id="dnsModeSwitch">
<button type="button" class="seg-btn active" data-mode="redir-host">redir-host</button>
<button type="button" class="seg-btn" data-mode="fake-ip">fake-ip</button>
</div>
</div>
<div class="form-row">
<label for="dnsNameserver">DNS серверы (по строке)</label>
<textarea id="dnsNameserver" rows="3" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.85rem;font-family:'JetBrains Mono','Fira Code',monospace;width:100%;resize:vertical">https://doh.pub/dns-query
https://dns.alidns.com/dns-query</textarea>
</div>
<div class="form-row">
<label for="dnsFallback">Fallback DNS (по строке)</label>
<textarea id="dnsFallback" rows="2" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.85rem;font-family:'JetBrains Mono','Fira Code',monospace;width:100%;resize:vertical">tls://8.8.8.8:853
tls://1.1.1.1:853</textarea>
</div>
<div class="form-divider"></div>
<div class="form-row">
<label for="externalController">External Controller</label>
<input type="text" id="externalController" value="0.0.0.0:9090">
</div>
<div class="form-row">
<label for="secret">Secret (API ключ)</label>
<input type="text" id="secret" placeholder="опционально">
</div>
<div style="margin-top:16px;display:flex;gap:8px">
<button type="submit" class="btn btn-primary">Сохранить настройки</button>
<button type="button" class="btn btn-ghost" id="saveAndRestartBtn">Сохранить и перезапустить</button>
</div>
</form>
<div class="form-divider" style="margin:20px 0"></div>
<h3 style="color:var(--text);font-size:.95rem;margin-bottom:8px">config.yaml (только чтение)</h3>
<div class="section-desc" style="margin-bottom:8px">Текущий конфиг mihomo. Обновляется автоматически после сохранения настроек.</div>
<div class="form-row">
<textarea id="yamlPreview" rows="18" readonly style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text-muted);padding:9px 12px;font-size:.8rem;font-family:'JetBrains Mono','Fira Code',monospace;width:100%;resize:vertical;opacity:.85"></textarea>
</div>
</div>
<!-- Logs Tab -->
<div id="tab-logs" class="ptab-content hidden">
<div class="section-header" style="margin-bottom:16px">
<div style="display:flex;align-items:center;justify-content:space-between">
<div>
<h2>Логи ядра</h2>
<div class="section-desc">Вывод процесса mihomo (обновление каждые 500мс)</div>
</div>
<button class="btn btn-ghost btn-sm" id="clearLogsBtn">Очистить</button>
</div>
</div>
<div id="logOutput" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;font-family:'JetBrains Mono','Fira Code',monospace;font-size:.78rem;color:var(--text-muted);min-height:200px;max-height:calc(100vh - 300px);overflow-y:auto;white-space:pre-wrap;word-break:break-all"></div>
</div>
<!-- Core Tab -->
<div id="tab-core" class="ptab-content hidden">
<div class="section-header" style="margin-bottom:16px">
<h2>Ядро Mihomo</h2>
<div class="section-desc">Управление бинарным файлом ядра</div>
</div>
<div id="coreStatus" class="proxy-card" style="margin-bottom:16px">
<div class="card-info">
<div class="info-row"><span class="info-label">Путь</span><span class="info-val" id="corePath"></span></div>
<div class="info-row"><span class="info-label">Наличие</span><span class="info-val" id="coreExists"></span></div>
<div class="info-row"><span class="info-label">PID</span><span class="info-val" id="corePid"></span></div>
</div>
</div>
<div class="form-divider" style="margin-bottom:16px"></div>
<h3 style="color:var(--text);font-size:.95rem;margin-bottom:12px">Загрузить ядро</h3>
<div class="section-desc" style="margin-bottom:12px">Загрузите бинарный файл mihomo (например, mihomo-linux-amd64). Файл автоматически определит архитектуру по имени.</div>
<form id="uploadCoreForm">
<div class="form-row">
<input type="file" id="coreFile" accept=".gz,.zip,application/octet-stream" style="color:var(--text)">
</div>
<button type="submit" class="btn btn-primary btn-sm" style="margin-top:8px">Загрузить</button>
</form>
<div class="form-divider" style="margin:20px 0"></div>
<h3 style="color:var(--text);font-size:.95rem;margin-bottom:12px">Ручная конфигурация (config.yaml)</h3>
<div class="section-desc" style="margin-bottom:8px">Редактировать конфигурационный файл напрямую</div>
<div class="form-row">
<textarea id="yamlEditor" rows="20" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.8rem;font-family:'JetBrains Mono','Fira Code',monospace;width:100%;resize:vertical"></textarea>
</div>
<div style="margin-top:8px;display:flex;gap:8px">
<button class="btn btn-primary btn-sm" id="yamlLoadBtn">Загрузить</button>
<button class="btn btn-success btn-sm" id="yamlSaveBtn">Сохранить</button>
</div>
</div>
</div>
<!-- Proxy Modal -->
<div id="proxyModal" class="modal hidden" role="dialog" aria-modal="true">
<div class="modal-backdrop" id="proxyModalBackdrop"></div>
<div class="modal-box" style="width:min(560px,calc(100vw - 40px))">
<div class="modal-header">
<h2 id="proxyModalTitle">Добавить прокси</h2>
<button class="btn-icon" id="closeProxyModal" title="Закрыть"></button>
</div>
<form id="proxyForm" autocomplete="off" style="max-height:60vh;overflow-y:auto">
<div class="form-row">
<label for="proxyType">Тип</label>
<select id="proxyType" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.9rem;width:100%">
<option value="ss">Shadowsocks</option>
<option value="vmess">VMess</option>
<option value="vless">VLESS</option>
<option value="trojan">Trojan</option>
<option value="hysteria2">Hysteria2</option>
<option value="http">HTTP</option>
<option value="socks5">SOCKS5</option>
<option value="direct">DIRECT</option>
</select>
</div>
<div class="form-row">
<label for="proxyName">Имя</label>
<input type="text" id="proxyName" placeholder="my-proxy">
</div>
<div id="proxyServerFields">
<div class="form-row">
<label for="proxyServer">Сервер</label>
<input type="text" id="proxyServer" placeholder="example.com">
</div>
<div class="form-row">
<label for="proxyPort">Порт</label>
<input type="number" id="proxyPort" placeholder="443">
</div>
</div>
<div id="proxyAuthFields" class="hidden">
<div class="form-row">
<label for="proxyUsername">Имя пользователя</label>
<input type="text" id="proxyUsername" placeholder="username">
</div>
<div class="form-row">
<label for="proxyPassword">Пароль</label>
<input type="text" id="proxyPassword" placeholder="password">
</div>
</div>
<div id="proxyCipherField" class="hidden">
<div class="form-row">
<label for="proxyCipher">Шифр</label>
<select id="proxyCipher" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.9rem;width:100%">
<option value="auto">auto</option>
<option value="aes-128-gcm">aes-128-gcm</option>
<option value="aes-256-gcm">aes-256-gcm</option>
<option value="chacha20-ietf-poly1305">chacha20-ietf-poly1305</option>
<option value="none">none</option>
</select>
</div>
</div>
<div id="proxyUUIDField" class="hidden">
<div class="form-row">
<label for="proxyUUID">UUID</label>
<input type="text" id="proxyUUID" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
</div>
</div>
<div id="proxyTrojanPassField" class="hidden">
<div class="form-row">
<label for="proxyTrojanPass">Пароль (Trojan)</label>
<input type="text" id="proxyTrojanPass" placeholder="password">
</div>
</div>
<div id="proxyHysteria2Fields" class="hidden">
<div class="form-row">
<label for="proxyObfs">Obfs тип</label>
<input type="text" id="proxyObfs" placeholder="salamander или пусто">
</div>
<div class="form-row">
<label for="proxyObfsPass">Obfs пароль</label>
<input type="text" id="proxyObfsPass" placeholder="obfs password">
</div>
</div>
<div id="proxyTLSField" class="hidden">
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="proxyTLS">
<span>TLS</span>
</label>
</div>
<div class="form-row">
<label for="proxySNI">SNI (ServerName)</label>
<input type="text" id="proxySNI" placeholder="example.com">
</div>
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="proxySkipCertVerify">
<span>Пропустить проверку сертификата</span>
</label>
</div>
</div>
<div id="proxyVlessFlowField" class="hidden">
<div class="form-row">
<label for="proxyFlow">Flow</label>
<input type="text" id="proxyFlow" placeholder="xtls-rprx-vision (опционально)">
</div>
</div>
<div id="proxyNetworkField" class="hidden">
<div class="form-row">
<label for="proxyNetwork">Транспорт</label>
<select id="proxyNetwork" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.9rem;width:100%">
<option value="">tcp (по умолчанию)</option>
<option value="ws">WebSocket</option>
<option value="grpc">gRPC</option>
<option value="h2">HTTP/2</option>
</select>
</div>
</div>
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="proxyUDP" checked>
<span>UDP</span>
</label>
</div>
<input type="hidden" id="proxyEditName" value="">
</form>
<div class="modal-footer">
<button class="btn btn-ghost" id="cancelProxyBtn">Отмена</button>
<button class="btn btn-primary" id="saveProxyBtn">Сохранить</button>
</div>
</div>
</div>
<!-- Group Modal -->
<div id="groupModal" class="modal hidden" role="dialog" aria-modal="true">
<div class="modal-backdrop" id="groupModalBackdrop"></div>
<div class="modal-box" style="width:min(560px,calc(100vw - 40px))">
<div class="modal-header">
<h2 id="groupModalTitle">Добавить группу</h2>
<button class="btn-icon" id="closeGroupModal" title="Закрыть"></button>
</div>
<form id="groupForm" autocomplete="off">
<div class="form-row">
<label for="groupName">Имя группы</label>
<input type="text" id="groupName" placeholder="proxy">
</div>
<div class="form-row">
<label for="groupType">Тип</label>
<select id="groupType" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.9rem;width:100%">
<option value="select">Select (ручной выбор)</option>
<option value="url-test">URL-test (автовыбор по задержке)</option>
<option value="fallback">Fallback (резервный)</option>
<option value="load-balance">Load Balance (балансировка)</option>
</select>
</div>
<div id="groupURLField" class="hidden">
<div class="form-row">
<label for="groupURL">URL тестирования</label>
<input type="text" id="groupURL" value="https://www.gstatic.com/generate_204" placeholder="https://www.gstatic.com/generate_204">
</div>
<div class="form-row">
<label for="groupInterval">Интервал тестирования (сек)</label>
<input type="number" id="groupInterval" value="300">
</div>
<div class="form-row">
<label for="groupTolerance">Допуск (мс)</label>
<input type="number" id="groupTolerance" value="50" placeholder="50">
</div>
</div>
<div id="groupLBStrategy" class="hidden">
<div class="form-row">
<label>Стратегия балансировки</label>
<select id="groupLBStrategy" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.9rem;width:100%">
<option value="round-robin">Round Robin</option>
<option value="consistent-hashing">Consistent Hashing</option>
<option value="sticky-sessions">Sticky Sessions</option>
</select>
</div>
</div>
<div class="form-row">
<label>Прокси-ноды в группе</label>
<div id="groupProxyCheckboxes" style="max-height:200px;overflow-y:auto;background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px"></div>
</div>
<div class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="groupIncludeAll">
<span>Включить все прокси автоматически</span>
</label>
</div>
<div class="form-row">
<label for="groupFilter">Фильтр (regex)</label>
<input type="text" id="groupFilter" placeholder="напр. (?i)hk|hongkong">
</div>
<input type="hidden" id="groupEditName" value="">
</form>
<div class="modal-footer">
<button class="btn btn-ghost" id="cancelGroupBtn">Отмена</button>
<button class="btn btn-primary" id="saveGroupBtn">Сохранить</button>
</div>
</div>
</div>
<!-- Rule Modal -->
<div id="ruleModal" class="modal hidden" role="dialog" aria-modal="true">
<div class="modal-backdrop" id="ruleModalBackdrop"></div>
<div class="modal-box" style="width:min(520px,calc(100vw - 40px))">
<div class="modal-header">
<h2 id="ruleModalTitle">Добавить правило</h2>
<button class="btn-icon" id="closeRuleModal" title="Закрыть"></button>
</div>
<form id="ruleForm" autocomplete="off">
<div class="form-row">
<label for="ruleType">Тип правила</label>
<select id="ruleType" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.9rem;width:100%">
<option value="DOMAIN">DOMAIN (точный домен)</option>
<option value="DOMAIN-SUFFIX">DOMAIN-SUFFIX (домен суффикс)</option>
<option value="DOMAIN-KEYWORD">DOMAIN-KEYWORD (ключевое слово)</option>
<option value="GEOSITE">GEOSITE (геосайт)</option>
<option value="GEOIP">GEOIP (гео IP)</option>
<option value="IP-CIDR">IP-CIDR (подсеть)</option>
<option value="SRC-IP-CIDR">SRC-IP-CIDR (источник подсеть)</option>
<option value="DST-PORT">DST-PORT (порт назначения)</option>
<option value="SRC-PORT">SRC-PORT (порт источника)</option>
<option value="MATCH">MATCH (всё)</option>
<option value="RULE-SET">RULE-SET (набор правил)</option>
</select>
</div>
<div id="ruleValueField" class="form-row">
<label for="ruleValue">Значение</label>
<input type="text" id="ruleValue" placeholder="напр. google.com или 192.168.0.0/16">
</div>
<div class="form-row">
<label for="ruleProxy">Прокси / Группа</label>
<select id="ruleProxy" style="background:var(--bg-deep);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:9px 12px;font-size:.9rem;width:100%">
<option value="DIRECT">DIRECT</option>
<option value="REJECT">REJECT</option>
</select>
</div>
<div id="ruleNoResolveDiv" class="form-row">
<label class="checkbox-label">
<input type="checkbox" id="ruleNoResolve" checked>
<span>no-resolve</span>
</label>
</div>
</form>
<div class="modal-footer">
<button class="btn btn-ghost" id="cancelRuleBtn">Отмена</button>
<button class="btn btn-primary" id="saveRuleBtn">Добавить</button>
</div>
</div>
</div>
<!-- Notification toast -->
<div id="toast" class="toast hidden"></div>
<script src="proxy.js"></script>
</body>
</html>