// OSTP Setup Wizard - Frontend Logic const { invoke } = window.__TAURI__.core; const { listen } = window.__TAURI__.event; // State let currentStep = 1; const totalSteps = 5; let systemCheckResult = null; let countries = []; let installPath = ''; // DOM Elements const stepContents = document.querySelectorAll('.step-content'); const stepIndicators = document.querySelectorAll('.step-indicator .step'); const btnNext = document.getElementById('btnNext'); const btnBack = document.getElementById('btnBack'); const btnCancel = document.getElementById('btnCancel'); // Initialize document.addEventListener('DOMContentLoaded', async () => { // Get default install path try { installPath = await invoke('get_default_install_path'); document.getElementById('installPath').value = installPath; } catch (e) { console.error('Failed to get install path:', e); document.getElementById('installPath').value = 'C:\\Program Files\\Ospab Network\\OSTP'; } // Load countries try { countries = await invoke('get_countries'); populateCountries(); } catch (e) { console.error('Failed to load countries:', e); } // Listen for installation progress listen('install-progress', (event) => { updateProgress(event.payload); }); // Setup event listeners setupEventListeners(); }); function setupEventListeners() { btnNext.addEventListener('click', handleNext); btnBack.addEventListener('click', handleBack); btnCancel.addEventListener('click', handleCancel); // Access key validation const accessKeyInput = document.getElementById('accessKey'); accessKeyInput.addEventListener('input', debounce(validateAccessKey, 300)); accessKeyInput.addEventListener('paste', () => { setTimeout(validateAccessKey, 100); }); } async function handleNext() { switch (currentStep) { case 1: // Welcome -> System Check goToStep(2); await runSystemCheck(); break; case 2: // System Check -> Configuration if (canProceedFromSystemCheck()) { goToStep(3); } break; case 3: // Configuration -> Installation if (await validateConfiguration()) { goToStep(4); await runInstallation(); } break; case 4: // Installation -> Finish (auto-transition on complete) break; case 5: // Finish -> Close await finishInstallation(); break; } } function handleBack() { if (currentStep > 1 && currentStep !== 4) { goToStep(currentStep - 1); } } function handleCancel() { if (confirm('Are you sure you want to cancel the installation?')) { window.close(); } } function goToStep(step) { // Update step indicators stepIndicators.forEach((indicator, index) => { indicator.classList.remove('active', 'completed'); if (index + 1 < step) { indicator.classList.add('completed'); } else if (index + 1 === step) { indicator.classList.add('active'); } }); // Update content visibility stepContents.forEach((content, index) => { content.classList.remove('active'); if (index + 1 === step) { content.classList.add('active'); } }); // Update button states btnBack.disabled = step === 1 || step === 4 || step === 5; if (step === 5) { btnNext.textContent = 'Finish'; } else if (step === 4) { btnNext.disabled = true; btnNext.textContent = 'Installing...'; } else { btnNext.disabled = false; btnNext.textContent = 'Next'; } currentStep = step; } // Step 2: System Check async function runSystemCheck() { const checkItems = { checkAdmin: document.getElementById('checkAdmin'), checkAesNi: document.getElementById('checkAesNi'), checkWintun: document.getElementById('checkWintun'), checkOs: document.getElementById('checkOs'), }; // Animate checking state Object.values(checkItems).forEach(item => { setCheckState(item, 'pending', '⏳', 'Checking...'); }); try { // Small delay for visual effect await sleep(500); systemCheckResult = await invoke('check_system'); // Update each check with results setCheckState( checkItems.checkAdmin, systemCheckResult.admin_privileges ? 'success' : 'error', systemCheckResult.admin_privileges ? '✓' : '✗', systemCheckResult.admin_privileges ? 'Elevated' : 'Required' ); await sleep(200); setCheckState( checkItems.checkAesNi, systemCheckResult.aes_ni_supported ? 'success' : 'warning', systemCheckResult.aes_ni_supported ? '✓' : '⚠', systemCheckResult.aes_ni_supported ? 'Supported' : 'Software fallback' ); await sleep(200); setCheckState( checkItems.checkWintun, 'success', systemCheckResult.wintun_installed ? '✓' : '◯', systemCheckResult.wintun_installed ? 'Installed' : 'Will be installed' ); await sleep(200); setCheckState( checkItems.checkOs, 'success', '✓', systemCheckResult.os_version ); // Enable next if admin check passed if (!systemCheckResult.admin_privileges) { btnNext.disabled = true; alert('Administrator privileges are required. Please restart the installer as Administrator.'); } } catch (e) { console.error('System check failed:', e); Object.values(checkItems).forEach(item => { setCheckState(item, 'error', '✗', 'Check failed'); }); } } function setCheckState(element, state, icon, status) { const iconEl = element.querySelector('.check-icon'); const statusEl = element.querySelector('.check-status'); iconEl.className = `check-icon ${state}`; iconEl.textContent = icon; statusEl.textContent = status; } function canProceedFromSystemCheck() { return systemCheckResult && systemCheckResult.admin_privileges; } // Step 3: Configuration function populateCountries() { const select = document.getElementById('country'); select.innerHTML = ''; countries.forEach(country => { const option = document.createElement('option'); option.value = country.code; option.textContent = country.name + (country.recommended ? ' (Recommended)' : ''); if (country.recommended) { option.selected = true; } select.appendChild(option); }); } async function validateAccessKey() { const input = document.getElementById('accessKey'); const errorEl = document.getElementById('accessKeyError'); const value = input.value.trim(); if (!value) { input.classList.remove('error'); errorEl.textContent = ''; return false; } try { const isValid = await invoke('validate_access_key', { key: value }); if (isValid) { input.classList.remove('error'); errorEl.textContent = ''; return true; } else { input.classList.add('error'); errorEl.textContent = 'Invalid access key format'; return false; } } catch (e) { input.classList.add('error'); errorEl.textContent = 'Failed to validate key'; return false; } } async function validateConfiguration() { const accessKey = document.getElementById('accessKey').value.trim(); const country = document.getElementById('country').value; if (!accessKey) { document.getElementById('accessKeyError').textContent = 'Access key is required'; document.getElementById('accessKey').classList.add('error'); return false; } if (!await validateAccessKey()) { return false; } if (!country) { alert('Please select a region'); return false; } return true; } // Step 4: Installation async function runInstallation() { const config = { access_key: document.getElementById('accessKey').value.trim(), country: document.getElementById('country').value, install_path: document.getElementById('installPath').value, launch_on_startup: true, // Will be read from step 5 connect_now: true, // Will be read from step 5 }; try { await invoke('run_installation', { config }); // Success - move to step 5 goToStep(5); } catch (e) { console.error('Installation failed:', e); alert('Installation failed: ' + e); btnNext.disabled = false; btnNext.textContent = 'Retry'; } } function updateProgress(progress) { const { step, total_steps, message, percent } = progress; // Update progress bar document.getElementById('progressFill').style.width = `${percent}%`; document.getElementById('progressText').textContent = `${percent}%`; document.getElementById('installStatus').textContent = message; // Update step indicators const installSteps = document.querySelectorAll('.install-step'); installSteps.forEach((stepEl, index) => { stepEl.classList.remove('active', 'completed'); const statusEl = stepEl.querySelector('.step-status'); if (index + 1 < step) { stepEl.classList.add('completed'); statusEl.textContent = '✓'; } else if (index + 1 === step) { stepEl.classList.add('active'); statusEl.textContent = '⏳'; } else { statusEl.textContent = '○'; } }); } // Step 5: Finish async function finishInstallation() { const connectNow = document.getElementById('optConnectNow').checked; const launchStartup = document.getElementById('optLaunchStartup').checked; // These would typically trigger additional backend calls // For now, just close the installer if (connectNow) { // Service should already be started by installation console.log('Connecting now...'); } window.close(); } // Utilities function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }