document.addEventListener('DOMContentLoaded', () => { // DOM Elements const form = document.getElementById('urlForm'); const hostnameInput = document.getElementById('hostname'); const domainSelect = document.getElementById('domain'); const portInput = document.getElementById('port'); const usernameInput = document.getElementById('username'); const passwordInput = document.getElementById('password'); const includeAuthCheck = document.getElementById('includeAuthCheck'); const authStatusText = document.getElementById('authStatusText'); const credentialsFields = document.getElementById('credentialsFields'); const preventSaveCheck = document.getElementById('preventPasswordSave'); const togglePasswordBtn = document.getElementById('togglePasswordVisibility'); const hostnameFeedback = document.getElementById('hostnameFeedback'); const urlDisplay = document.getElementById('urlDisplay'); const launchBtn = document.getElementById('launchBtn'); const copyBtn = document.getElementById('copyBtn'); const copyBtnText = document.getElementById('copyBtnText'); const autoOpenNotice = document.getElementById('autoOpenNotice'); // Validation patterns: IPv4, Router Serial (alphanumeric 10-14 chars), and FQDN const serialRegex = /^[a-zA-Z0-9]{10,14}$/; const ipv4Regex = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/; const domainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/; let currentWinboxUri = ''; // Validate and categorize user input function validateHostname(raw) { let clean = raw.trim(); let port = null; // Extract port if typed directly into host field (e.g., 10.0.0.1:8292) const portMatch = clean.match(/:(\d{1,5})$/); if (portMatch) { port = portMatch[1]; clean = clean.replace(/:\d{1,5}$/, ''); } if (!clean) { hostnameFeedback.innerHTML = ''; return { type: 'empty', value: '', port }; } if (ipv4Regex.test(clean)) { hostnameFeedback.innerHTML = 'IPv4 Address'; return { type: 'ipv4', value: clean, port }; } if (serialRegex.test(clean)) { hostnameFeedback.innerHTML = 'Router Serial'; return { type: 'serial', value: clean, port }; } if (domainRegex.test(clean)) { hostnameFeedback.innerHTML = 'Domain / FQDN'; return { type: 'domain', value: clean, port }; } hostnameFeedback.innerHTML = 'Unrecognized Format'; return { type: 'raw', value: clean, port }; } // Convert router serial into MikroTik cloud DDNS or VPN hostname function resolveHost(value, type) { if (type === 'serial') { return domainSelect.value === 'vpn' ? `${value.toLowerCase()}.mt.vpn.sn` : `${value.toLowerCase()}.sn.mynetname.net`; } return value; } // Build the winbox:// URI and update the UI function updateUi() { const val = validateHostname(hostnameInput.value); if (val.port) { portInput.value = val.port; } const host = resolveHost(val.value, val.type); const port = portInput.value.trim(); const includeAuth = includeAuthCheck.checked; const username = includeAuth ? usernameInput.value.trim() : ''; const password = includeAuth ? passwordInput.value : ''; if (!host) { currentWinboxUri = ''; urlDisplay.textContent = 'winbox://'; launchBtn.disabled = true; launchBtn.classList.add('opacity-50', 'cursor-not-allowed'); return; } launchBtn.disabled = false; launchBtn.classList.remove('opacity-50', 'cursor-not-allowed'); let uri = 'winbox://'; let displayUri = 'winbox://'; if (username && password) { uri += `${encodeURIComponent(username)}:${encodeURIComponent(password)}@`; displayUri += `${encodeURIComponent(username)}:••••••••@`; } else if (username) { uri += `${encodeURIComponent(username)}@`; displayUri += `${encodeURIComponent(username)}@`; } uri += host; displayUri += host; if (port && port !== '8291') { uri += `:${port}`; displayUri += `:${port}`; } currentWinboxUri = uri; urlDisplay.textContent = displayUri; } // Launch action function openWinbox() { if (!currentWinboxUri) return; window.location.href = currentWinboxUri; } // Form submission form.addEventListener('submit', (e) => { e.preventDefault(); openWinbox(); }); // Copy to clipboard copyBtn.addEventListener('click', async () => { if (!currentWinboxUri) return; try { await navigator.clipboard.writeText(currentWinboxUri); copyBtnText.textContent = 'Copied!'; setTimeout(() => { copyBtnText.textContent = 'Copy URI'; }, 1800); } catch { prompt('Copy WinBox URI:', currentWinboxUri); } }); // Password visibility toggle togglePasswordBtn.addEventListener('click', () => { const isPassword = passwordInput.type === 'password'; passwordInput.type = isPassword ? 'text' : 'password'; togglePasswordBtn.innerHTML = isPassword ? `` : ``; }); // Toggle credentials block on/off includeAuthCheck.addEventListener('change', () => { const enabled = includeAuthCheck.checked; authStatusText.textContent = enabled ? 'Include' : 'Omit'; credentialsFields.classList.toggle('disabled-state', !enabled); updateUi(); }); // Disallow browser password save prompt preventSaveCheck.addEventListener('change', () => { if (preventSaveCheck.checked) { passwordInput.setAttribute('autocomplete', 'new-password'); usernameInput.setAttribute('autocomplete', 'off'); } else { passwordInput.setAttribute('autocomplete', 'current-password'); usernameInput.setAttribute('autocomplete', 'username'); } }); // Handle incoming URL parameters (e.g., ?hostname=192.168.88.1&user=admin&autoclose=1) function handleQueryParams() { const params = new URLSearchParams(window.location.search); const hostParam = params.get('hostname') || params.get('host'); if (!hostParam) return; hostnameInput.value = hostParam; if (params.get('port')) portInput.value = params.get('port'); if (params.get('username') || params.get('user')) usernameInput.value = params.get('username') || params.get('user'); if (params.get('password') || params.get('pass')) passwordInput.value = params.get('password') || params.get('pass'); if (params.get('domain')) domainSelect.value = params.get('domain'); updateUi(); if (currentWinboxUri) { openWinbox(); autoOpenNotice.classList.remove('hidden'); if (params.get('autoclose') === '1') { setTimeout(() => { window.close(); }, 500); } } } // Input listeners [hostnameInput, domainSelect, portInput, usernameInput, passwordInput].forEach(el => { el.addEventListener('input', updateUi); }); // Initialization updateUi(); handleQueryParams(); });