feat: добавляет визуализацию цепочки прокси, настройки подключения и интерфейс для конфигурации резервного прокси.

This commit is contained in:
2026-01-15 00:34:46 +03:00
parent 13c92c7413
commit 116856c1d1
4 changed files with 652 additions and 24 deletions

View File

@@ -238,9 +238,183 @@
</div>
</div>
<!-- Proxy Chain Visualization -->
<div id="proxyChainSection" class="bg-black border border-[#00ff41]/20 p-4 font-mono">
<div
class="text-[9px] uppercase font-bold tracking-[0.3em] text-[#00ff41] mb-4 flex items-center gap-2">
<i data-lucide="git-branch" class="w-3 h-3"></i> Proxy_Chain
</div>
<div id="proxyChain" class="flex items-stretch gap-3 text-[10px]">
<!-- You -->
<div class="flex flex-col items-center justify-center gap-1">
<div
class="w-10 h-10 rounded-full border-2 border-[#00ff41] flex items-center justify-center bg-[#00ff41]/10">
<i data-lucide="user" class="w-5 h-5 text-[#00ff41]"></i>
</div>
<span class="uppercase opacity-60 text-[#00ff41] text-[8px]">You</span>
</div>
<!-- Arrow to branch -->
<div class="flex items-center">
<div class="w-6 h-[2px] bg-[#00ff41]"></div>
<i data-lucide="chevron-right" class="w-3 h-3 text-[#00ff41]"></i>
</div>
<!-- Branch: Fallback + VPN -->
<div id="chainBranch" class="flex flex-col gap-2 py-1">
<!-- Fallback branch -->
<div id="chainFallbackRow" class="flex items-center gap-2 transition-all duration-300 hidden">
<div class="w-4 h-[2px] bg-[#00ff41]/50 rounded-full"></div>
<div id="chainFallbackBox"
class="relative w-14 h-9 border-2 border-[#00ff41]/30 flex items-center justify-center bg-[#0a0a0a] transition-all">
<i data-lucide="server" class="w-4 h-4 text-[#00ff41]/50"></i>
<!-- X overlay for unavailable -->
<div id="chainFallbackX"
class="absolute inset-0 hidden items-center justify-center bg-black/60">
<i data-lucide="x" class="w-5 h-5 text-red-500"></i>
</div>
</div>
<span id="chainFallbackLabel"
class="uppercase text-[7px] opacity-40 text-[#00ff41] max-w-[55px] truncate">Fallback</span>
</div>
<!-- VPN branch -->
<div id="chainVPNRow" class="flex items-center gap-2 transition-all duration-300">
<div class="w-4 h-[2px] bg-[#00ff41]/50 rounded-full"></div>
<div id="chainVPNBox"
class="relative w-14 h-9 border-2 border-[#00ff41]/30 flex items-center justify-center bg-[#0a0a0a] transition-all">
<i data-lucide="shield" class="w-4 h-4 text-[#00ff41]/50"></i>
<!-- X overlay for unavailable -->
<div id="chainVPNX"
class="absolute inset-0 hidden items-center justify-center bg-black/60">
<i data-lucide="x" class="w-5 h-5 text-red-500"></i>
</div>
</div>
<span id="chainVPNLabel"
class="uppercase text-[7px] opacity-40 text-[#00ff41] max-w-[55px] truncate">VPN</span>
</div>
</div>
<!-- Arrow from branch -->
<div class="flex items-center">
<i data-lucide="chevron-right" class="w-3 h-3 text-[#00ff41]"></i>
<div class="w-6 h-[2px] bg-[#00ff41]"></div>
</div>
<!-- Internet -->
<div class="flex flex-col items-center justify-center gap-1">
<div
class="w-10 h-10 rounded-full border-2 border-blue-400 flex items-center justify-center bg-blue-400/10">
<i data-lucide="globe" class="w-5 h-5 text-blue-400"></i>
</div>
<span class="uppercase opacity-60 text-blue-400 text-[8px]">Net</span>
</div>
</div>
<!-- Status message -->
<div id="chainStatus" class="mt-3 text-[9px] text-center opacity-50 text-[#00ff41] uppercase">
No proxy configured
</div>
</div>
<!-- Connection Settings -->
<div class="bg-black border border-[#00ff41]/20 p-4 font-mono">
<div
class="text-[9px] uppercase font-bold tracking-[0.3em] text-[#00ff41] mb-3 flex items-center gap-2">
<i data-lucide="plug" class="w-3 h-3"></i> Connection_Settings
</div>
<div class="grid grid-cols-1 gap-2">
<!-- HTTP Proxy -->
<div class="flex items-center gap-2 p-2 bg-[#0a0a0a] border border-[#00ff41]/10">
<span class="text-[8px] uppercase opacity-50 text-[#00ff41] w-12">HTTP</span>
<input type="text" id="httpProxyUrl" readonly value="Loading..."
class="flex-grow bg-transparent text-[10px] text-[#00ff41]/70 focus:outline-none cursor-pointer font-mono"
title="Click to copy" />
<button onclick="copyToClipboard('httpProxyUrl', this)"
class="text-[8px] text-[#00ff41]/50 hover:text-[#00ff41] transition-colors uppercase">
Copy
</button>
</div>
<!-- SOCKS5 Proxy -->
<div class="flex items-center gap-2 p-2 bg-[#0a0a0a] border border-[#00ff41]/10">
<span class="text-[8px] uppercase opacity-50 text-[#00ff41] w-12">SOCKS5</span>
<input type="text" id="socks5ProxyUrl" readonly value="Loading..."
class="flex-grow bg-transparent text-[10px] text-[#00ff41]/70 focus:outline-none cursor-pointer font-mono"
title="Click to copy" />
<button onclick="copyToClipboard('socks5ProxyUrl', this)"
class="text-[8px] text-[#00ff41]/50 hover:text-[#00ff41] transition-colors uppercase">
Copy
</button>
</div>
</div>
<div class="mt-2 text-[8px] opacity-30 text-[#00ff41]">
<i data-lucide="info" class="w-3 h-3 inline"></i>
Use these URLs in browser/app proxy settings
</div>
</div>
<!-- Fallback Proxy Configuration -->
<div id="fallbackSection"
class="flex flex-col bg-black border border-[#00ff41]/20 overflow-hidden font-mono transition-opacity">
<div
class="bg-[#111] px-4 py-2 border-b border-[#00ff41]/10 flex justify-between items-center shrink-0">
<span
class="text-[9px] uppercase font-bold tracking-[0.3em] flex items-center gap-2 text-[#00ff41]">
<i data-lucide="git-branch" class="w-3 h-3"></i> Fallback_Proxy
</span>
<div class="flex items-center gap-3">
<!-- Enable/Disable Toggle -->
<label class="flex items-center gap-2 cursor-pointer">
<span id="fallbackToggleLabel"
class="text-[8px] opacity-50 uppercase text-[#00ff41]">OFF</span>
<div class="relative">
<input type="checkbox" id="fallbackToggle" class="sr-only peer">
<div
class="w-8 h-4 bg-[#1a1a1a] border border-[#00ff41]/20 rounded-full peer-checked:bg-[#00ff41]/20 peer-checked:border-[#00ff41]/50 transition-all">
</div>
<div
class="absolute left-0.5 top-0.5 w-3 h-3 bg-[#00ff41]/30 rounded-full peer-checked:translate-x-4 peer-checked:bg-[#00ff41] transition-all">
</div>
</div>
</label>
<button id="saveFallbackBtn"
class="text-[8px] opacity-30 hover:opacity-100 hover:text-[#00ff41] transition-opacity uppercase">Save</button>
</div>
</div>
<div class="p-3 space-y-3">
<!-- Host/Port inputs -->
<div class="grid grid-cols-3 gap-2">
<div class="col-span-2">
<label class="text-[8px] opacity-40 uppercase text-[#00ff41] block mb-1">Host</label>
<input type="text" id="fallbackHost" placeholder="192.168.50.111"
class="w-full bg-[#0a0a0a] border border-[#00ff41]/10 p-2 text-[10px] text-[#00ff41]/80 focus:outline-none focus:border-[#00ff41]/30" />
</div>
<div>
<label class="text-[8px] opacity-40 uppercase text-[#00ff41] block mb-1">Port</label>
<input type="number" id="fallbackPort" placeholder="8080"
class="w-full bg-[#0a0a0a] border border-[#00ff41]/10 p-2 text-[10px] text-[#00ff41]/80 focus:outline-none focus:border-[#00ff41]/30" />
</div>
</div>
<!-- Info -->
<div class="text-[8px] opacity-40 text-[#00ff41]">
<i data-lucide="info" class="w-3 h-3 inline"></i>
URLTest auto-selects fastest proxy. Re-apply subscription after changes.
</div>
<!-- Status -->
<div id="fallbackStatus" class="text-[9px] text-[#00ff41]/50 uppercase hidden"></div>
</div>
</div>
<!-- Terminal Logs -->
<div
class="flex-grow flex flex-col bg-black border border-[#00ff41]/20 overflow-hidden font-mono h-[300px] lg:h-[400px]">
class="flex-grow flex flex-col bg-black border border-[#00ff41]/20 overflow-hidden font-mono h-[200px] lg:h-[250px]">
<div
class="bg-[#111] px-4 py-2 border-b border-[#00ff41]/10 flex justify-between items-center shrink-0">
<span
@@ -315,7 +489,26 @@
headerStatus: document.getElementById('headerStatus'),
sessionId: document.getElementById('sessionId'),
trafficValue: document.getElementById('trafficValue'),
testSpeedBtn: document.getElementById('testSpeedBtn')
testSpeedBtn: document.getElementById('testSpeedBtn'),
// Fallback Proxy elements
fallbackToggle: document.getElementById('fallbackToggle'),
fallbackToggleLabel: document.getElementById('fallbackToggleLabel'),
fallbackHost: document.getElementById('fallbackHost'),
fallbackPort: document.getElementById('fallbackPort'),
saveFallbackBtn: document.getElementById('saveFallbackBtn'),
fallbackStatus: document.getElementById('fallbackStatus'),
// Proxy Chain visualization
chainFallbackRow: document.getElementById('chainFallbackRow'),
chainFallbackBox: document.getElementById('chainFallbackBox'),
chainFallbackLabel: document.getElementById('chainFallbackLabel'),
chainFallbackX: document.getElementById('chainFallbackX'),
chainVPNBox: document.getElementById('chainVPNBox'),
chainVPNLabel: document.getElementById('chainVPNLabel'),
chainVPNX: document.getElementById('chainVPNX'),
chainStatus: document.getElementById('chainStatus'),
// Connection settings
httpProxyUrl: document.getElementById('httpProxyUrl'),
socks5ProxyUrl: document.getElementById('socks5ProxyUrl')
};
els.sessionId.textContent = state.sessionId;
@@ -357,6 +550,21 @@
}
}
// Global copy function for proxy URLs
function copyToClipboard(inputId, btn) {
const input = document.getElementById(inputId);
navigator.clipboard.writeText(input.value).then(() => {
const originalText = btn.textContent;
btn.textContent = 'Copied!';
btn.classList.add('text-blue-400');
setTimeout(() => {
btn.textContent = originalText;
btn.classList.remove('text-blue-400');
}, 1500);
addLog(`COPIED: ${input.value}`, 'success');
});
}
els.clearLogs.addEventListener('click', () => {
const lastChild = els.logsContainer.lastElementChild;
els.logsContainer.innerHTML = '';
@@ -624,6 +832,12 @@
const res = await fetch('/status');
const data = await res.json();
// Update proxy URLs with actual port
if (data.proxyPort) {
els.httpProxyUrl.value = `http://127.0.0.1:${data.proxyPort}`;
els.socks5ProxyUrl.value = `socks5://127.0.0.1:${data.proxyPort}`;
}
if (data.active && data.tag) {
const currentTag = state.activeNode ? state.activeNode.tag : null;
@@ -689,9 +903,151 @@
checkConnectionSpeed(true);
});
// --- Fallback Proxy Functions ---
async function loadFallbackConfig() {
try {
const res = await fetch('/fallback-config');
const data = await res.json();
els.fallbackToggle.checked = data.enabled || false;
els.fallbackHost.value = data.host || '192.168.50.111';
els.fallbackPort.value = data.port || 8080;
updateFallbackUI(data.enabled || false);
addLog('FALLBACK_CONFIG_LOADED', 'info');
} catch (e) {
addLog('FALLBACK_CONFIG_LOAD_FAILED', 'error');
}
}
function updateFallbackUI(enabled) {
els.fallbackToggleLabel.textContent = enabled ? 'ON' : 'OFF';
els.fallbackToggleLabel.classList.toggle('opacity-100', enabled);
els.fallbackToggleLabel.classList.toggle('opacity-50', !enabled);
}
async function saveFallbackConfig() {
const enabled = els.fallbackToggle.checked;
const host = els.fallbackHost.value.trim();
const port = parseInt(els.fallbackPort.value) || 8080;
if (enabled && !host) {
addLog('FALLBACK_HOST_REQUIRED', 'error');
showFallbackStatus('Host is required', 'error');
return;
}
try {
const res = await fetch('/fallback-config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled, host, port })
});
const data = await res.json();
if (data.success) {
addLog(enabled ? 'FALLBACK_ENABLED' : 'FALLBACK_DISABLED', 'success');
const msg = data.regenerated ? 'Applied!' : 'Saved!';
showFallbackStatus(msg, 'success');
updateFallbackUI(enabled);
// Refresh proxy chain visualization
updateProxyChain();
} else {
throw new Error(data.error);
}
} catch (e) {
addLog(`FALLBACK_SAVE_FAILED: ${e.message}`, 'error');
showFallbackStatus('Save failed', 'error');
}
}
function showFallbackStatus(msg, type = 'info') {
els.fallbackStatus.textContent = msg;
els.fallbackStatus.classList.remove('hidden', 'text-[#00ff41]/50', 'text-red-500', 'text-blue-400');
if (type === 'success') els.fallbackStatus.classList.add('text-blue-400');
else if (type === 'error') els.fallbackStatus.classList.add('text-red-500');
else els.fallbackStatus.classList.add('text-[#00ff41]/50');
setTimeout(() => {
els.fallbackStatus.classList.add('hidden');
}, 3000);
}
// --- Proxy Chain Visualization ---
async function updateProxyChain() {
try {
const res = await fetch('/active-proxy');
const data = await res.json();
// Reset all states
els.chainFallbackBox.classList.remove('border-[#00ff41]', 'bg-[#00ff41]/20');
els.chainFallbackBox.classList.add('border-[#00ff41]/30');
els.chainVPNBox.classList.remove('border-[#00ff41]', 'bg-[#00ff41]/20');
els.chainVPNBox.classList.add('border-[#00ff41]/30');
els.chainFallbackX.classList.add('hidden');
els.chainFallbackX.classList.remove('flex');
els.chainVPNX.classList.add('hidden');
els.chainVPNX.classList.remove('flex');
if (!data.configured) {
els.chainStatus.textContent = 'No proxy configured';
els.chainFallbackRow.classList.add('hidden');
els.chainVPNLabel.textContent = 'VPN';
return;
}
// Update VPN label
els.chainVPNLabel.textContent = data.vpnTag || 'VPN';
if (data.fallbackEnabled) {
// Show fallback branch
els.chainFallbackRow.classList.remove('hidden');
els.chainFallbackLabel.textContent = data.fallbackHost || 'Fallback';
if (data.fallbackReachable) {
// Fallback is active (green border, no X)
els.chainFallbackBox.classList.remove('border-[#00ff41]/30');
els.chainFallbackBox.classList.add('border-[#00ff41]', 'bg-[#00ff41]/20');
els.chainStatus.innerHTML = `<span class="text-[#00ff41]">●</span> Fallback active (${data.fallbackLatency}ms)`;
} else {
// Fallback unreachable - show X, VPN is active
els.chainFallbackX.classList.remove('hidden');
els.chainFallbackX.classList.add('flex');
els.chainVPNBox.classList.remove('border-[#00ff41]/30');
els.chainVPNBox.classList.add('border-[#00ff41]', 'bg-[#00ff41]/20');
els.chainStatus.innerHTML = `<span class="text-yellow-400">●</span> VPN active (fallback down)`;
}
} else {
// No fallback - hide fallback row, VPN is active
els.chainFallbackRow.classList.add('hidden');
els.chainVPNBox.classList.remove('border-[#00ff41]/30');
els.chainVPNBox.classList.add('border-[#00ff41]', 'bg-[#00ff41]/20');
els.chainStatus.innerHTML = `<span class="text-[#00ff41]">●</span> VPN direct`;
}
// Reinitialize lucide icons for new X elements
lucide.createIcons();
} catch (e) {
els.chainStatus.textContent = 'Failed to load';
}
}
// Fallback Event Listeners
els.saveFallbackBtn.addEventListener('click', saveFallbackConfig);
els.fallbackToggle.addEventListener('change', saveFallbackConfig);
// --- Init ---
addLog('TERMINAL_READY', 'info');
loadSaved();
loadFallbackConfig();
updateProxyChain();
// Periodically update proxy chain
setInterval(updateProxyChain, 10000);
// Blink animation style
const style = document.createElement('style');