Files
ospab.network/ostp-setup/ui/app.js
ospab 7e1c87e70b feat: Windows Setup Wizard (ostp-setup) with Tauri
- Tauri 2.0 based graphical installer
- Access Key parsing with AES-256-GCM encryption
- Windows Service installation via sc.exe
- WinTUN driver extraction from embedded resources
- System requirements checking (admin, AES-NI, OS version)
- Modern dark UI with step-by-step wizard flow
- Country/region selection for SNI mimicry
2026-01-01 21:49:37 +03:00

361 lines
10 KiB
JavaScript

// 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);
};
}