975 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			975 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!DOCTYPE html>
 | 
						||
<html lang="en">
 | 
						||
<head>
 | 
						||
    <meta charset="UTF-8">
 | 
						||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
						||
    <title>VPN Gateway Control - Multi Provider</title>
 | 
						||
    <style>
 | 
						||
        * {
 | 
						||
            margin: 0;
 | 
						||
            padding: 0;
 | 
						||
            box-sizing: border-box;
 | 
						||
        }
 | 
						||
 | 
						||
        body {
 | 
						||
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
 | 
						||
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
						||
            min-height: 100vh;
 | 
						||
            padding: 20px;
 | 
						||
        }
 | 
						||
 | 
						||
        .container {
 | 
						||
            max-width: 1200px;
 | 
						||
            margin: 0 auto;
 | 
						||
        }
 | 
						||
 | 
						||
        .header {
 | 
						||
            text-align: center;
 | 
						||
            color: white;
 | 
						||
            margin-bottom: 30px;
 | 
						||
        }
 | 
						||
 | 
						||
        .header h1 {
 | 
						||
            font-size: 2.5em;
 | 
						||
            margin-bottom: 10px;
 | 
						||
            text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
 | 
						||
        }
 | 
						||
 | 
						||
        .provider-selector {
 | 
						||
            background: white;
 | 
						||
            border-radius: 15px;
 | 
						||
            padding: 30px;
 | 
						||
            margin-bottom: 30px;
 | 
						||
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
 | 
						||
        }
 | 
						||
 | 
						||
        .provider-tabs {
 | 
						||
            display: flex;
 | 
						||
            gap: 10px;
 | 
						||
            margin-bottom: 30px;
 | 
						||
            flex-wrap: wrap;
 | 
						||
        }
 | 
						||
 | 
						||
        .provider-tab {
 | 
						||
            flex: 1;
 | 
						||
            min-width: 150px;
 | 
						||
            padding: 15px 25px;
 | 
						||
            border: 2px solid #e0e0e0;
 | 
						||
            border-radius: 10px;
 | 
						||
            background: white;
 | 
						||
            cursor: pointer;
 | 
						||
            transition: all 0.3s;
 | 
						||
            text-align: center;
 | 
						||
            font-weight: 600;
 | 
						||
        }
 | 
						||
 | 
						||
        .provider-tab:hover {
 | 
						||
            transform: translateY(-2px);
 | 
						||
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
 | 
						||
        }
 | 
						||
 | 
						||
        .provider-tab.active {
 | 
						||
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
						||
            color: white;
 | 
						||
            border-color: transparent;
 | 
						||
        }
 | 
						||
 | 
						||
        .main-grid {
 | 
						||
            display: grid;
 | 
						||
            grid-template-columns: 1fr 1fr;
 | 
						||
            gap: 30px;
 | 
						||
        }
 | 
						||
 | 
						||
        @media (max-width: 768px) {
 | 
						||
            .main-grid {
 | 
						||
                grid-template-columns: 1fr;
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        .card {
 | 
						||
            background: rgba(255, 255, 255, 0.95);
 | 
						||
            backdrop-filter: blur(10px);
 | 
						||
            border-radius: 15px;
 | 
						||
            padding: 30px;
 | 
						||
            box-shadow: 0 10px 40px rgba(0,0,0,0.1);
 | 
						||
        }
 | 
						||
 | 
						||
        .status-card {
 | 
						||
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
						||
            color: white;
 | 
						||
        }
 | 
						||
 | 
						||
        .status-header {
 | 
						||
            display: flex;
 | 
						||
            justify-content: space-between;
 | 
						||
            align-items: center;
 | 
						||
            margin-bottom: 25px;
 | 
						||
        }
 | 
						||
 | 
						||
        .status-indicator {
 | 
						||
            display: flex;
 | 
						||
            align-items: center;
 | 
						||
            gap: 10px;
 | 
						||
            font-size: 1.3em;
 | 
						||
            font-weight: bold;
 | 
						||
        }
 | 
						||
 | 
						||
        .status-dot {
 | 
						||
            width: 15px;
 | 
						||
            height: 15px;
 | 
						||
            border-radius: 50%;
 | 
						||
            animation: pulse 2s infinite;
 | 
						||
        }
 | 
						||
 | 
						||
        .status-dot.connected {
 | 
						||
            background: #4ade80;
 | 
						||
            box-shadow: 0 0 10px #4ade80;
 | 
						||
        }
 | 
						||
 | 
						||
        .status-dot.disconnected {
 | 
						||
            background: #f87171;
 | 
						||
            box-shadow: 0 0 10px #f87171;
 | 
						||
        }
 | 
						||
 | 
						||
        @keyframes pulse {
 | 
						||
            0%, 100% { transform: scale(1); }
 | 
						||
            50% { transform: scale(1.2); }
 | 
						||
        }
 | 
						||
 | 
						||
        .info-grid {
 | 
						||
            display: grid;
 | 
						||
            grid-template-columns: 1fr 1fr;
 | 
						||
            gap: 15px;
 | 
						||
        }
 | 
						||
 | 
						||
        .info-item {
 | 
						||
            background: rgba(255, 255, 255, 0.1);
 | 
						||
            padding: 15px;
 | 
						||
            border-radius: 10px;
 | 
						||
            backdrop-filter: blur(5px);
 | 
						||
        }
 | 
						||
 | 
						||
        .info-label {
 | 
						||
            font-size: 0.9em;
 | 
						||
            opacity: 0.9;
 | 
						||
            margin-bottom: 5px;
 | 
						||
        }
 | 
						||
 | 
						||
        .info-value {
 | 
						||
            font-size: 1.2em;
 | 
						||
            font-weight: bold;
 | 
						||
        }
 | 
						||
 | 
						||
        .security-notice {
 | 
						||
            background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
 | 
						||
            color: white;
 | 
						||
            padding: 20px;
 | 
						||
            border-radius: 10px;
 | 
						||
            margin-bottom: 20px;
 | 
						||
        }
 | 
						||
 | 
						||
        select, input, textarea {
 | 
						||
            width: 100%;
 | 
						||
            padding: 15px;
 | 
						||
            border: 2px solid #e0e0e0;
 | 
						||
            border-radius: 10px;
 | 
						||
            font-size: 1em;
 | 
						||
            margin-bottom: 15px;
 | 
						||
            transition: all 0.3s;
 | 
						||
        }
 | 
						||
 | 
						||
        select:focus, input:focus, textarea:focus {
 | 
						||
            outline: none;
 | 
						||
            border-color: #667eea;
 | 
						||
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
 | 
						||
        }
 | 
						||
 | 
						||
        button {
 | 
						||
            padding: 15px 30px;
 | 
						||
            border: none;
 | 
						||
            border-radius: 10px;
 | 
						||
            font-size: 1.1em;
 | 
						||
            font-weight: 600;
 | 
						||
            cursor: pointer;
 | 
						||
            transition: all 0.3s;
 | 
						||
            width: 100%;
 | 
						||
            margin-bottom: 10px;
 | 
						||
        }
 | 
						||
 | 
						||
        .btn-primary {
 | 
						||
            background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
 | 
						||
            color: white;
 | 
						||
        }
 | 
						||
 | 
						||
        .btn-primary:hover {
 | 
						||
            transform: translateY(-2px);
 | 
						||
            box-shadow: 0 10px 20px rgba(74, 222, 128, 0.3);
 | 
						||
        }
 | 
						||
 | 
						||
        .btn-danger {
 | 
						||
            background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
 | 
						||
            color: white;
 | 
						||
        }
 | 
						||
 | 
						||
        .btn-secondary {
 | 
						||
            background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
 | 
						||
            color: white;
 | 
						||
        }
 | 
						||
 | 
						||
        .custom-server-form {
 | 
						||
            display: none;
 | 
						||
            background: #f8f9fa;
 | 
						||
            padding: 20px;
 | 
						||
            border-radius: 10px;
 | 
						||
            margin-top: 20px;
 | 
						||
        }
 | 
						||
 | 
						||
        .custom-server-form.active {
 | 
						||
            display: block;
 | 
						||
        }
 | 
						||
 | 
						||
        .server-list {
 | 
						||
            max-height: 300px;
 | 
						||
            overflow-y: auto;
 | 
						||
            margin-top: 20px;
 | 
						||
        }
 | 
						||
 | 
						||
        .server-item {
 | 
						||
            display: flex;
 | 
						||
            justify-content: space-between;
 | 
						||
            align-items: center;
 | 
						||
            padding: 15px;
 | 
						||
            background: white;
 | 
						||
            border-radius: 10px;
 | 
						||
            margin-bottom: 10px;
 | 
						||
            transition: all 0.3s;
 | 
						||
        }
 | 
						||
 | 
						||
        .server-item:hover {
 | 
						||
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
 | 
						||
        }
 | 
						||
 | 
						||
        .import-section {
 | 
						||
            background: #f8f9fa;
 | 
						||
            padding: 20px;
 | 
						||
            border-radius: 10px;
 | 
						||
            margin-top: 20px;
 | 
						||
        }
 | 
						||
 | 
						||
        .file-upload {
 | 
						||
            position: relative;
 | 
						||
            display: inline-block;
 | 
						||
            cursor: pointer;
 | 
						||
            width: 100%;
 | 
						||
        }
 | 
						||
 | 
						||
        .file-upload input[type=file] {
 | 
						||
            position: absolute;
 | 
						||
            left: -9999px;
 | 
						||
        }
 | 
						||
 | 
						||
        .file-upload label {
 | 
						||
            display: block;
 | 
						||
            padding: 15px;
 | 
						||
            background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
 | 
						||
            color: white;
 | 
						||
            border-radius: 10px;
 | 
						||
            text-align: center;
 | 
						||
            cursor: pointer;
 | 
						||
            transition: all 0.3s;
 | 
						||
        }
 | 
						||
 | 
						||
        .file-upload label:hover {
 | 
						||
            transform: translateY(-2px);
 | 
						||
            box-shadow: 0 10px 20px rgba(96, 165, 250, 0.3);
 | 
						||
        }
 | 
						||
 | 
						||
        .tabs {
 | 
						||
            display: flex;
 | 
						||
            gap: 10px;
 | 
						||
            margin-bottom: 20px;
 | 
						||
        }
 | 
						||
 | 
						||
        .tab {
 | 
						||
            padding: 10px 20px;
 | 
						||
            background: #e0e0e0;
 | 
						||
            border-radius: 10px;
 | 
						||
            cursor: pointer;
 | 
						||
            transition: all 0.3s;
 | 
						||
        }
 | 
						||
 | 
						||
        .tab.active {
 | 
						||
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
						||
            color: white;
 | 
						||
        }
 | 
						||
 | 
						||
        .tab-content {
 | 
						||
            display: none;
 | 
						||
        }
 | 
						||
 | 
						||
        .tab-content.active {
 | 
						||
            display: block;
 | 
						||
        }
 | 
						||
 | 
						||
        .spinner {
 | 
						||
            display: none;
 | 
						||
            width: 20px;
 | 
						||
            height: 20px;
 | 
						||
            border: 3px solid rgba(255, 255, 255, 0.3);
 | 
						||
            border-top-color: white;
 | 
						||
            border-radius: 50%;
 | 
						||
            animation: spin 1s linear infinite;
 | 
						||
            margin: 0 auto;
 | 
						||
        }
 | 
						||
 | 
						||
        @keyframes spin {
 | 
						||
            to { transform: rotate(360deg); }
 | 
						||
        }
 | 
						||
 | 
						||
        .loading .spinner {
 | 
						||
            display: block;
 | 
						||
        }
 | 
						||
 | 
						||
        .modal {
 | 
						||
            display: none;
 | 
						||
            position: fixed;
 | 
						||
            top: 0;
 | 
						||
            left: 0;
 | 
						||
            width: 100%;
 | 
						||
            height: 100%;
 | 
						||
            background: rgba(0, 0, 0, 0.5);
 | 
						||
            z-index: 1000;
 | 
						||
            align-items: center;
 | 
						||
            justify-content: center;
 | 
						||
        }
 | 
						||
 | 
						||
        .modal.active {
 | 
						||
            display: flex;
 | 
						||
        }
 | 
						||
 | 
						||
        .modal-content {
 | 
						||
            background: white;
 | 
						||
            padding: 30px;
 | 
						||
            border-radius: 15px;
 | 
						||
            max-width: 500px;
 | 
						||
            width: 90%;
 | 
						||
            max-height: 80vh;
 | 
						||
            overflow-y: auto;
 | 
						||
        }
 | 
						||
 | 
						||
        .keypair-display {
 | 
						||
            background: #f8f9fa;
 | 
						||
            padding: 15px;
 | 
						||
            border-radius: 10px;
 | 
						||
            margin: 10px 0;
 | 
						||
            font-family: monospace;
 | 
						||
            word-break: break-all;
 | 
						||
            font-size: 0.9em;
 | 
						||
        }
 | 
						||
 | 
						||
        .copy-btn {
 | 
						||
            padding: 5px 10px;
 | 
						||
            background: #667eea;
 | 
						||
            color: white;
 | 
						||
            border: none;
 | 
						||
            border-radius: 5px;
 | 
						||
            cursor: pointer;
 | 
						||
            font-size: 0.9em;
 | 
						||
        }
 | 
						||
    </style>
 | 
						||
</head>
 | 
						||
<body>
 | 
						||
    <div class="container">
 | 
						||
        <div class="header">
 | 
						||
            <h1>🔐 VPN Gateway Control Center</h1>
 | 
						||
            <p>Multi-Provider Support with Permanent Killswitch</p>
 | 
						||
        </div>
 | 
						||
 | 
						||
        <div class="provider-selector">
 | 
						||
            <h2>Select VPN Provider</h2>
 | 
						||
            <div class="provider-tabs">
 | 
						||
                <div class="provider-tab active" data-provider="mullvad" onclick="switchProvider('mullvad')">
 | 
						||
                    <div>🌍 Mullvad</div>
 | 
						||
                    <small>Commercial VPN</small>
 | 
						||
                </div>
 | 
						||
                <div class="provider-tab" data-provider="custom" onclick="switchProvider('custom')">
 | 
						||
                    <div>🔧 Custom Server</div>
 | 
						||
                    <small>Own VPS/Server</small>
 | 
						||
                </div>
 | 
						||
                <div class="provider-tab" data-provider="imported" onclick="switchProvider('imported')">
 | 
						||
                    <div>📁 Import Config</div>
 | 
						||
                    <small>Existing WireGuard</small>
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
        </div>
 | 
						||
 | 
						||
        <div class="main-grid">
 | 
						||
            <div class="card status-card">
 | 
						||
                <div class="status-header">
 | 
						||
                    <div class="status-indicator">
 | 
						||
                        <span class="status-dot disconnected" id="statusDot"></span>
 | 
						||
                        <span id="statusText">Disconnected</span>
 | 
						||
                    </div>
 | 
						||
                    <button class="btn-secondary" onclick="refreshStatus()" style="width: auto; padding: 10px 20px;">
 | 
						||
                        🔄 Refresh
 | 
						||
                    </button>
 | 
						||
                </div>
 | 
						||
                
 | 
						||
                <div class="info-grid">
 | 
						||
                    <div class="info-item">
 | 
						||
                        <div class="info-label">Provider</div>
 | 
						||
                        <div class="info-value" id="currentProvider">-</div>
 | 
						||
                    </div>
 | 
						||
                    <div class="info-item">
 | 
						||
                        <div class="info-label">Server</div>
 | 
						||
                        <div class="info-value" id="currentServer">-</div>
 | 
						||
                    </div>
 | 
						||
                    <div class="info-item">
 | 
						||
                        <div class="info-label">Public IP</div>
 | 
						||
                        <div class="info-value" id="publicIP">-</div>
 | 
						||
                    </div>
 | 
						||
                    <div class="info-item">
 | 
						||
                        <div class="info-label">Location</div>
 | 
						||
                        <div class="info-value" id="location">-</div>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
 | 
						||
                <div class="security-notice" style="margin-top: 20px;">
 | 
						||
                    <strong>🛡️ Security Status: PROTECTED</strong><br>
 | 
						||
                    ✓ Killswitch permanently active<br>
 | 
						||
                    ✓ No internet without VPN<br>
 | 
						||
                    ✓ DNS leak protection enabled
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
 | 
						||
            <div class="card">
 | 
						||
                <!-- Mullvad Provider Section -->
 | 
						||
                <div id="mullvad-section" class="provider-section">
 | 
						||
                    <h3>Mullvad Server Selection</h3>
 | 
						||
                    <select id="countrySelect" onchange="updateCities()">
 | 
						||
                        <option value="">Loading countries...</option>
 | 
						||
                    </select>
 | 
						||
                    
 | 
						||
                    <select id="citySelect" onchange="updateServers()">
 | 
						||
                        <option value="">Select a country first</option>
 | 
						||
                    </select>
 | 
						||
                    
 | 
						||
                    <select id="serverSelect">
 | 
						||
                        <option value="">Select a city first</option>
 | 
						||
                    </select>
 | 
						||
                </div>
 | 
						||
 | 
						||
                <!-- Custom Provider Section -->
 | 
						||
                <div id="custom-section" class="provider-section" style="display: none;">
 | 
						||
                    <h3>Custom WireGuard Servers</h3>
 | 
						||
                    
 | 
						||
                    <button class="btn-secondary" onclick="toggleCustomForm()">
 | 
						||
                        ➕ Add New Server
 | 
						||
                    </button>
 | 
						||
                    
 | 
						||
                    <div id="customServerForm" class="custom-server-form">
 | 
						||
                        <h4>Add Custom Server</h4>
 | 
						||
                        <input type="text" id="customName" placeholder="Server Name (e.g., my-vps)">
 | 
						||
                        <input type="text" id="customEndpoint" placeholder="Endpoint (IP:Port or domain:port)">
 | 
						||
                        <input type="text" id="customPublicKey" placeholder="Server Public Key">
 | 
						||
                        <input type="text" id="customPrivateKey" placeholder="Client Private Key (optional)">
 | 
						||
                        <input type="text" id="customAddress" placeholder="Client IP (default: 10.0.0.2/32)">
 | 
						||
                        <input type="text" id="customDNS" placeholder="DNS Servers (default: 1.1.1.1,1.0.0.1)">
 | 
						||
                        <input type="text" id="customLocation" placeholder="Location (e.g., Germany)">
 | 
						||
                        
 | 
						||
                        <button class="btn-secondary" onclick="generateKeypair()">
 | 
						||
                            🔑 Generate Keypair
 | 
						||
                        </button>
 | 
						||
                        
 | 
						||
                        <button class="btn-primary" onclick="addCustomServer()">
 | 
						||
                            Add Server
 | 
						||
                        </button>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div class="server-list" id="customServerList">
 | 
						||
                        <!-- Custom servers will be listed here -->
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <select id="customServerSelect">
 | 
						||
                        <option value="">Select a custom server</option>
 | 
						||
                    </select>
 | 
						||
                </div>
 | 
						||
 | 
						||
                <!-- Import Provider Section -->
 | 
						||
                <div id="import-section" class="provider-section" style="display: none;">
 | 
						||
                    <h3>Import WireGuard Configuration</h3>
 | 
						||
                    
 | 
						||
                    <div class="import-section">
 | 
						||
                        <input type="text" id="importName" placeholder="Configuration Name">
 | 
						||
                        
 | 
						||
                        <div class="file-upload">
 | 
						||
                            <input type="file" id="configFile" accept=".conf" onchange="handleFileSelect(event)">
 | 
						||
                            <label for="configFile">📁 Choose WireGuard Config File</label>
 | 
						||
                        </div>
 | 
						||
                        
 | 
						||
                        <textarea id="configContent" placeholder="Or paste your WireGuard config here..." rows="10"></textarea>
 | 
						||
                        
 | 
						||
                        <button class="btn-primary" onclick="importConfig()">
 | 
						||
                            Import Configuration
 | 
						||
                        </button>
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <div class="server-list" id="importedConfigList">
 | 
						||
                        <!-- Imported configs will be listed here -->
 | 
						||
                    </div>
 | 
						||
                    
 | 
						||
                    <select id="importedServerSelect">
 | 
						||
                        <option value="">Select an imported config</option>
 | 
						||
                    </select>
 | 
						||
                </div>
 | 
						||
 | 
						||
                <div style="margin-top: 30px;">
 | 
						||
                    <button class="btn-primary" onclick="connectVPN()">
 | 
						||
                        <span>🔗 Connect</span>
 | 
						||
                        <span class="spinner"></span>
 | 
						||
                    </button>
 | 
						||
                    <button class="btn-danger" onclick="disconnectVPN()">
 | 
						||
                        <span>⛔ Disconnect</span>
 | 
						||
                        <span class="spinner"></span>
 | 
						||
                    </button>
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
        </div>
 | 
						||
    </div>
 | 
						||
 | 
						||
    <!-- Keypair Modal -->
 | 
						||
    <div id="keypairModal" class="modal">
 | 
						||
        <div class="modal-content">
 | 
						||
            <h3>Generated WireGuard Keypair</h3>
 | 
						||
            <p style="margin: 15px 0;">Save these keys securely!</p>
 | 
						||
            
 | 
						||
            <label>Private Key (Keep Secret!):</label>
 | 
						||
            <div class="keypair-display" id="generatedPrivateKey"></div>
 | 
						||
            <button class="copy-btn" onclick="copyToClipboard('generatedPrivateKey')">Copy</button>
 | 
						||
            
 | 
						||
            <label style="margin-top: 15px; display: block;">Public Key (Share with Server):</label>
 | 
						||
            <div class="keypair-display" id="generatedPublicKey"></div>
 | 
						||
            <button class="copy-btn" onclick="copyToClipboard('generatedPublicKey')">Copy</button>
 | 
						||
            
 | 
						||
            <button class="btn-secondary" style="margin-top: 20px;" onclick="closeModal()">
 | 
						||
                Close
 | 
						||
            </button>
 | 
						||
        </div>
 | 
						||
    </div>
 | 
						||
 | 
						||
    <script>
 | 
						||
        let currentProvider = 'mullvad';
 | 
						||
        let servers = {};
 | 
						||
        let customServers = [];
 | 
						||
        let importedConfigs = [];
 | 
						||
 | 
						||
        // Initialize
 | 
						||
        document.addEventListener('DOMContentLoaded', function() {
 | 
						||
            loadProviders();
 | 
						||
            refreshStatus();
 | 
						||
            setInterval(refreshStatus, 10000);
 | 
						||
        });
 | 
						||
 | 
						||
        async function loadProviders() {
 | 
						||
            try {
 | 
						||
                const response = await fetch('/api/providers');
 | 
						||
                const data = await response.json();
 | 
						||
                
 | 
						||
                if (data.current) {
 | 
						||
                    currentProvider = data.current;
 | 
						||
                    switchProvider(currentProvider);
 | 
						||
                } else {
 | 
						||
                    loadServers();
 | 
						||
                }
 | 
						||
            } catch (error) {
 | 
						||
                console.error('Error loading providers:', error);
 | 
						||
                loadServers();
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        async function switchProvider(provider) {
 | 
						||
            // Update UI
 | 
						||
            document.querySelectorAll('.provider-tab').forEach(tab => {
 | 
						||
                tab.classList.remove('active');
 | 
						||
            });
 | 
						||
            document.querySelector(`[data-provider="${provider}"]`).classList.add('active');
 | 
						||
            
 | 
						||
            // Hide all sections
 | 
						||
            document.querySelectorAll('.provider-section').forEach(section => {
 | 
						||
                section.style.display = 'none';
 | 
						||
            });
 | 
						||
            
 | 
						||
            // Show selected section
 | 
						||
            document.getElementById(`${provider}-section`).style.display = 'block';
 | 
						||
            
 | 
						||
            currentProvider = provider;
 | 
						||
            
 | 
						||
            // Switch backend provider
 | 
						||
            try {
 | 
						||
                await fetch(`/api/provider/${provider}`, { method: 'POST' });
 | 
						||
            } catch (error) {
 | 
						||
                console.error('Error switching provider:', error);
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Load servers for new provider
 | 
						||
            loadServers();
 | 
						||
        }
 | 
						||
 | 
						||
        async function loadServers() {
 | 
						||
            try {
 | 
						||
                const response = await fetch('/api/servers');
 | 
						||
                const data = await response.json();
 | 
						||
                
 | 
						||
                servers = data.servers;
 | 
						||
                
 | 
						||
                if (currentProvider === 'mullvad') {
 | 
						||
                    updateMullvadSelects();
 | 
						||
                } else if (currentProvider === 'custom') {
 | 
						||
                    updateCustomServers();
 | 
						||
                } else if (currentProvider === 'imported') {
 | 
						||
                    updateImportedConfigs();
 | 
						||
                }
 | 
						||
            } catch (error) {
 | 
						||
                console.error('Error loading servers:', error);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        function updateMullvadSelects() {
 | 
						||
            const countrySelect = document.getElementById('countrySelect');
 | 
						||
            countrySelect.innerHTML = '<option value="">Select a country</option>';
 | 
						||
            
 | 
						||
            Object.keys(servers).forEach(country => {
 | 
						||
                const option = document.createElement('option');
 | 
						||
                option.value = country;
 | 
						||
                option.textContent = country;
 | 
						||
                countrySelect.appendChild(option);
 | 
						||
            });
 | 
						||
        }
 | 
						||
 | 
						||
        function updateCities() {
 | 
						||
            const country = document.getElementById('countrySelect').value;
 | 
						||
            const citySelect = document.getElementById('citySelect');
 | 
						||
            const serverSelect = document.getElementById('serverSelect');
 | 
						||
            
 | 
						||
            citySelect.innerHTML = '<option value="">Select a city</option>';
 | 
						||
            serverSelect.innerHTML = '<option value="">Select a city first</option>';
 | 
						||
            
 | 
						||
            if (country && servers[country]) {
 | 
						||
                Object.keys(servers[country]).forEach(city => {
 | 
						||
                    const option = document.createElement('option');
 | 
						||
                    option.value = city;
 | 
						||
                    option.textContent = city;
 | 
						||
                    citySelect.appendChild(option);
 | 
						||
                });
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        function updateServers() {
 | 
						||
            const country = document.getElementById('countrySelect').value;
 | 
						||
            const city = document.getElementById('citySelect').value;
 | 
						||
            const serverSelect = document.getElementById('serverSelect');
 | 
						||
            
 | 
						||
            serverSelect.innerHTML = '<option value="">Select a server</option>';
 | 
						||
            
 | 
						||
            if (country && city && servers[country][city]) {
 | 
						||
                servers[country][city].forEach(server => {
 | 
						||
                    const option = document.createElement('option');
 | 
						||
                    option.value = server.hostname;
 | 
						||
                    option.textContent = `${server.hostname} (${server.type || 'WireGuard'})`;
 | 
						||
                    serverSelect.appendChild(option);
 | 
						||
                });
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        function updateCustomServers() {
 | 
						||
            const customSelect = document.getElementById('customServerSelect');
 | 
						||
            const serverList = document.getElementById('customServerList');
 | 
						||
            
 | 
						||
            customSelect.innerHTML = '<option value="">Select a custom server</option>';
 | 
						||
            serverList.innerHTML = '';
 | 
						||
            
 | 
						||
            // Flatten servers structure for custom
 | 
						||
            for (const location in servers) {
 | 
						||
                for (const city in servers[location]) {
 | 
						||
                    servers[location][city].forEach(server => {
 | 
						||
                        // Add to select
 | 
						||
                        const option = document.createElement('option');
 | 
						||
                        option.value = server.hostname;
 | 
						||
                        option.textContent = `${server.hostname} (${location})`;
 | 
						||
                        customSelect.appendChild(option);
 | 
						||
                        
 | 
						||
                        // Add to list
 | 
						||
                        const item = document.createElement('div');
 | 
						||
                        item.className = 'server-item';
 | 
						||
                        item.innerHTML = `
 | 
						||
                            <div>
 | 
						||
                                <strong>${server.hostname}</strong><br>
 | 
						||
                                <small>${server.endpoint} - ${location}</small>
 | 
						||
                            </div>
 | 
						||
                            <button class="btn-danger" style="width: auto; padding: 5px 15px;" 
 | 
						||
                                    onclick="removeCustomServer('${server.hostname}')">
 | 
						||
                                Remove
 | 
						||
                            </button>
 | 
						||
                        `;
 | 
						||
                        serverList.appendChild(item);
 | 
						||
                    });
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        function updateImportedConfigs() {
 | 
						||
            const importedSelect = document.getElementById('importedServerSelect');
 | 
						||
            const configList = document.getElementById('importedConfigList');
 | 
						||
            
 | 
						||
            importedSelect.innerHTML = '<option value="">Select an imported config</option>';
 | 
						||
            configList.innerHTML = '';
 | 
						||
            
 | 
						||
            // Check for imported configs in servers
 | 
						||
            if (servers['Imported'] && servers['Imported']['Configs']) {
 | 
						||
                servers['Imported']['Configs'].forEach(config => {
 | 
						||
                    // Add to select
 | 
						||
                    const option = document.createElement('option');
 | 
						||
                    option.value = config.hostname;
 | 
						||
                    option.textContent = config.hostname;
 | 
						||
                    importedSelect.appendChild(option);
 | 
						||
                    
 | 
						||
                    // Add to list
 | 
						||
                    const item = document.createElement('div');
 | 
						||
                    item.className = 'server-item';
 | 
						||
                    item.innerHTML = `
 | 
						||
                        <div>
 | 
						||
                            <strong>${config.hostname}</strong><br>
 | 
						||
                            <small>Imported Configuration</small>
 | 
						||
                        </div>
 | 
						||
                    `;
 | 
						||
                    configList.appendChild(item);
 | 
						||
                });
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        function toggleCustomForm() {
 | 
						||
            const form = document.getElementById('customServerForm');
 | 
						||
            form.classList.toggle('active');
 | 
						||
        }
 | 
						||
 | 
						||
        async function generateKeypair() {
 | 
						||
            try {
 | 
						||
                const response = await fetch('/api/keypair');
 | 
						||
                const data = await response.json();
 | 
						||
                
 | 
						||
                document.getElementById('customPrivateKey').value = data.private_key;
 | 
						||
                document.getElementById('generatedPrivateKey').textContent = data.private_key;
 | 
						||
                document.getElementById('generatedPublicKey').textContent = data.public_key;
 | 
						||
                document.getElementById('keypairModal').classList.add('active');
 | 
						||
                
 | 
						||
            } catch (error) {
 | 
						||
                alert('Error generating keypair: ' + error);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        async function addCustomServer() {
 | 
						||
            const name = document.getElementById('customName').value;
 | 
						||
            const endpoint = document.getElementById('customEndpoint').value;
 | 
						||
            const publicKey = document.getElementById('customPublicKey').value;
 | 
						||
            
 | 
						||
            if (!name || !endpoint || !publicKey) {
 | 
						||
                alert('Please fill in required fields');
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            const config = {
 | 
						||
                name: name,
 | 
						||
                endpoint: endpoint,
 | 
						||
                public_key: publicKey,
 | 
						||
                private_key: document.getElementById('customPrivateKey').value,
 | 
						||
                address: document.getElementById('customAddress').value,
 | 
						||
                dns: document.getElementById('customDNS').value,
 | 
						||
                location: document.getElementById('customLocation').value || 'Custom'
 | 
						||
            };
 | 
						||
            
 | 
						||
            try {
 | 
						||
                const response = await fetch('/api/custom/add', {
 | 
						||
                    method: 'POST',
 | 
						||
                    headers: {'Content-Type': 'application/json'},
 | 
						||
                    body: JSON.stringify(config)
 | 
						||
                });
 | 
						||
                
 | 
						||
                if (response.ok) {
 | 
						||
                    alert('Server added successfully');
 | 
						||
                    toggleCustomForm();
 | 
						||
                    // Clear form
 | 
						||
                    document.querySelectorAll('#customServerForm input').forEach(input => input.value = '');
 | 
						||
                    loadServers();
 | 
						||
                } else {
 | 
						||
                    alert('Failed to add server');
 | 
						||
                }
 | 
						||
            } catch (error) {
 | 
						||
                alert('Error adding server: ' + error);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        async function removeCustomServer(name) {
 | 
						||
            if (!confirm(`Remove server ${name}?`)) return;
 | 
						||
            
 | 
						||
            try {
 | 
						||
                const response = await fetch(`/api/custom/remove/${name}`, {
 | 
						||
                    method: 'DELETE'
 | 
						||
                });
 | 
						||
                
 | 
						||
                if (response.ok) {
 | 
						||
                    loadServers();
 | 
						||
                } else {
 | 
						||
                    alert('Failed to remove server');
 | 
						||
                }
 | 
						||
            } catch (error) {
 | 
						||
                alert('Error removing server: ' + error);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        function handleFileSelect(event) {
 | 
						||
            const file = event.target.files[0];
 | 
						||
            if (file) {
 | 
						||
                const reader = new FileReader();
 | 
						||
                reader.onload = function(e) {
 | 
						||
                    document.getElementById('configContent').value = e.target.result;
 | 
						||
                    
 | 
						||
                    // Try to extract name from filename
 | 
						||
                    const name = file.name.replace('.conf', '');
 | 
						||
                    document.getElementById('importName').value = name;
 | 
						||
                };
 | 
						||
                reader.readAsText(file);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        async function importConfig() {
 | 
						||
            const name = document.getElementById('importName').value;
 | 
						||
            const config = document.getElementById('configContent').value;
 | 
						||
            
 | 
						||
            if (!name || !config) {
 | 
						||
                alert('Please provide a name and configuration');
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            try {
 | 
						||
                const response = await fetch('/api/import', {
 | 
						||
                    method: 'POST',
 | 
						||
                    headers: {'Content-Type': 'application/json'},
 | 
						||
                    body: JSON.stringify({name: name, config: config})
 | 
						||
                });
 | 
						||
                
 | 
						||
                if (response.ok) {
 | 
						||
                    alert('Configuration imported successfully');
 | 
						||
                    document.getElementById('importName').value = '';
 | 
						||
                    document.getElementById('configContent').value = '';
 | 
						||
                    loadServers();
 | 
						||
                } else {
 | 
						||
                    alert('Failed to import configuration');
 | 
						||
                }
 | 
						||
            } catch (error) {
 | 
						||
                alert('Error importing config: ' + error);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        async function refreshStatus() {
 | 
						||
            try {
 | 
						||
                const response = await fetch('/api/status');
 | 
						||
                const data = await response.json();
 | 
						||
                
 | 
						||
                const statusDot = document.getElementById('statusDot');
 | 
						||
                const statusText = document.getElementById('statusText');
 | 
						||
                
 | 
						||
                if (data.connected) {
 | 
						||
                    statusDot.className = 'status-dot connected';
 | 
						||
                    statusText.textContent = 'Connected';
 | 
						||
                    document.getElementById('currentProvider').textContent = data.provider || '-';
 | 
						||
                    document.getElementById('currentServer').textContent = data.server || '-';
 | 
						||
                    document.getElementById('publicIP').textContent = data.ip || 'Checking...';
 | 
						||
                    document.getElementById('location').textContent = data.location || '-';
 | 
						||
                } else {
 | 
						||
                    statusDot.className = 'status-dot disconnected';
 | 
						||
                    statusText.textContent = 'Disconnected';
 | 
						||
                    document.getElementById('currentProvider').textContent = '-';
 | 
						||
                    document.getElementById('currentServer').textContent = '-';
 | 
						||
                    document.getElementById('publicIP').textContent = '-';
 | 
						||
                    document.getElementById('location').textContent = '-';
 | 
						||
                }
 | 
						||
            } catch (error) {
 | 
						||
                console.error('Error refreshing status:', error);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        async function connectVPN() {
 | 
						||
            let server = null;
 | 
						||
            
 | 
						||
            if (currentProvider === 'mullvad') {
 | 
						||
                server = document.getElementById('serverSelect').value;
 | 
						||
            } else if (currentProvider === 'custom') {
 | 
						||
                server = document.getElementById('customServerSelect').value;
 | 
						||
            } else if (currentProvider === 'imported') {
 | 
						||
                server = document.getElementById('importedServerSelect').value;
 | 
						||
            }
 | 
						||
            
 | 
						||
            if (!server) {
 | 
						||
                alert('Please select a server');
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            const button = event.target.closest('button');
 | 
						||
            button.classList.add('loading');
 | 
						||
            
 | 
						||
            try {
 | 
						||
                const response = await fetch('/api/connect', {
 | 
						||
                    method: 'POST',
 | 
						||
                    headers: {'Content-Type': 'application/json'},
 | 
						||
                    body: JSON.stringify({server: server})
 | 
						||
                });
 | 
						||
                
 | 
						||
                const data = await response.json();
 | 
						||
                
 | 
						||
                if (data.success) {
 | 
						||
                    alert('Connected successfully!');
 | 
						||
                    setTimeout(refreshStatus, 2000);
 | 
						||
                } else {
 | 
						||
                    alert('Connection failed: ' + (data.error || 'Unknown error'));
 | 
						||
                }
 | 
						||
            } catch (error) {
 | 
						||
                alert('Connection error: ' + error);
 | 
						||
            } finally {
 | 
						||
                button.classList.remove('loading');
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        async function disconnectVPN() {
 | 
						||
            const button = event.target.closest('button');
 | 
						||
            button.classList.add('loading');
 | 
						||
            
 | 
						||
            try {
 | 
						||
                const response = await fetch('/api/disconnect', {method: 'POST'});
 | 
						||
                const data = await response.json();
 | 
						||
                
 | 
						||
                if (data.success) {
 | 
						||
                    alert('Disconnected - WARNING: No internet access (killswitch active)');
 | 
						||
                    setTimeout(refreshStatus, 1000);
 | 
						||
                } else {
 | 
						||
                    alert('Disconnect failed: ' + (data.error || 'Unknown error'));
 | 
						||
                }
 | 
						||
            } catch (error) {
 | 
						||
                alert('Disconnect error: ' + error);
 | 
						||
            } finally {
 | 
						||
                button.classList.remove('loading');
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        function copyToClipboard(elementId) {
 | 
						||
            const text = document.getElementById(elementId).textContent;
 | 
						||
            navigator.clipboard.writeText(text).then(() => {
 | 
						||
                alert('Copied to clipboard!');
 | 
						||
            });
 | 
						||
        }
 | 
						||
 | 
						||
        function closeModal() {
 | 
						||
            document.getElementById('keypairModal').classList.remove('active');
 | 
						||
        }
 | 
						||
    </script>
 | 
						||
</body>
 | 
						||
</html>
 |