ostp/ostp-gui/src/main.js

223 lines
6.6 KiB
JavaScript

const { invoke } = window.__TAURI__.core;
// State management
let appState = 'disconnected'; // 'disconnected', 'connecting', 'connected'
let pollInterval = null;
let elapsedSeconds = 0;
let elapsedTimer = null;
// DOM Elements
const btnConnect = document.getElementById('btn-connect');
const powerContainer = document.querySelector('.power-button-container');
const statusText = document.getElementById('status-text');
const uptimeText = document.getElementById('uptime-text');
const metricDown = document.getElementById('metric-down');
const metricUp = document.getElementById('metric-up');
const homeScreen = document.getElementById('home-screen');
const settingsScreen = document.getElementById('settings-screen');
const btnGoSettings = document.getElementById('btn-go-settings');
const btnBack = document.getElementById('btn-back');
const btnSaveConfig = document.getElementById('btn-save-config');
const configEditor = document.getElementById('config-editor');
const configToast = document.getElementById('config-toast');
// Utils
function formatBytes(bytes) {
if (bytes === 0) return '0.0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function formatTime(seconds) {
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return [
hrs > 0 ? String(hrs).padStart(2, '0') : null,
String(mins).padStart(2, '0'),
String(secs).padStart(2, '0')
].filter(x => x !== null).join(':');
}
// State Updates
function setUIState(state) {
appState = state;
// Clean up classes
btnConnect.className = 'power-btn';
powerContainer.className = 'power-button-container';
statusText.className = '';
if (state === 'disconnected') {
statusText.textContent = 'Disconnected';
statusText.classList.add('status-disconnected');
uptimeText.textContent = 'Tap to protect your traffic';
clearInterval(pollInterval);
clearInterval(elapsedTimer);
pollInterval = null;
elapsedTimer = null;
elapsedSeconds = 0;
} else if (state === 'connecting') {
btnConnect.classList.add('connecting');
powerContainer.classList.add('connecting');
statusText.textContent = 'Connecting...';
statusText.classList.add('status-connecting');
uptimeText.textContent = 'Establishing secure tunnel';
} else if (state === 'connected') {
btnConnect.classList.add('connected');
powerContainer.classList.add('connected');
statusText.textContent = 'Protected';
statusText.classList.add('status-connected');
// Start poll timer
if (!pollInterval) {
pollInterval = setInterval(fetchMetrics, 1000);
}
// Start uptime timer
if (!elapsedTimer) {
elapsedSeconds = 0;
elapsedTimer = setInterval(() => {
elapsedSeconds++;
uptimeText.textContent = `Uptime: ${formatTime(elapsedSeconds)}`;
}, 1000);
}
}
}
// UI Event Handlers
async function handleToggleConnect() {
if (appState === 'disconnected') {
setUIState('connecting');
try {
const success = await invoke('start_tunnel');
if (success) {
// The start_tunnel call waits briefly or returns if spawn worked
// Backend will periodically check status. Let's monitor it.
monitorTunnelState();
} else {
alert('Failed to start tunnel process. Check config.json');
setUIState('disconnected');
}
} catch (err) {
alert('Error launching tunnel: ' + err);
setUIState('disconnected');
}
} else {
try {
await invoke('stop_tunnel');
} catch (err) {
console.error(err);
}
setUIState('disconnected');
}
}
async function monitorTunnelState() {
// Check status for up to 5 seconds to confirm it connects
let attempts = 0;
const check = async () => {
try {
const isAlive = await invoke('get_tunnel_status');
if (isAlive) {
setUIState('connected');
return true;
}
} catch (e) {}
attempts++;
if (attempts < 5 && appState === 'connecting') {
setTimeout(check, 1000);
} else if (appState === 'connecting') {
alert('Tunnel failed to stay alive. Make sure you run with Admin privileges if using TUN mode.');
setUIState('disconnected');
}
};
setTimeout(check, 1500); // Delay initial check to give it time to boot
}
async function fetchMetrics() {
try {
const stats = await invoke('get_metrics'); // Expected format: { bytes_sent: u64, bytes_recv: u64 }
if (stats) {
metricDown.textContent = formatBytes(stats.bytes_recv);
metricUp.textContent = formatBytes(stats.bytes_sent);
}
} catch (e) {
console.error('Failed to fetch metrics', e);
}
// Also verify process is still alive
try {
const isAlive = await invoke('get_tunnel_status');
if (!isAlive && appState === 'connected') {
setUIState('disconnected');
}
} catch (e) {}
}
function switchScreen(target) {
if (target === 'settings') {
loadConfigText();
homeScreen.classList.remove('active');
settingsScreen.classList.add('active');
} else {
settingsScreen.classList.remove('active');
homeScreen.classList.add('active');
}
}
async function loadConfigText() {
configEditor.value = 'Loading configuration...';
try {
const rawConfig = await invoke('get_config');
configEditor.value = rawConfig;
} catch (err) {
configEditor.value = '// Error loading configuration: ' + err;
}
}
async function handleSaveConfig() {
try {
const val = configEditor.value;
JSON.parse(val); // Validate JSON format first
const success = await invoke('save_config', { jsonContent: val });
if (success) {
showToast();
setTimeout(() => switchScreen('home'), 800);
}
} catch (err) {
alert('Invalid JSON or saving failed: ' + err.message);
}
}
function showToast() {
configToast.classList.add('show');
setTimeout(() => configToast.classList.remove('show'), 2000);
}
// Initialization
window.addEventListener('DOMContentLoaded', async () => {
btnConnect.addEventListener('click', handleToggleConnect);
btnGoSettings.addEventListener('click', () => switchScreen('settings'));
btnBack.addEventListener('click', () => switchScreen('home'));
btnSaveConfig.addEventListener('click', handleSaveConfig);
// Check current status on startup (reconnect UI if process already active)
try {
const isAlive = await invoke('get_tunnel_status');
if (isAlive) {
setUIState('connected');
} else {
setUIState('disconnected');
}
} catch (err) {
setUIState('disconnected');
}
});