feat: Windows stack (daemon, installer, GUI)

Components:
- ostp-daemon: Windows Service with Named Pipe IPC
- ostp-installer: Setup wizard with admin privileges
- ostp-gui: Tauri dark theme UI (450x600)

Features:
- Background service management (OspabGuard)
- IPC commands: CONNECT/DISCONNECT/STATUS
- Firewall rules auto-configuration
- Wintun driver placeholder (download from wintun.net)
- Real-time stats display (upload/download/ping)

Note: Requires wintun.dll download for full functionality
This commit is contained in:
2026-01-02 02:17:15 +03:00
parent 7ed4217987
commit 85a2b01074
40 changed files with 1460 additions and 7376 deletions

117
ostp-gui/ui/app.js Normal file
View File

@@ -0,0 +1,117 @@
const { invoke } = window.__TAURI__.core;
let isConnected = false;
// Initialize
document.addEventListener('DOMContentLoaded', async () => {
await loadServers();
await updateStatus();
// Update stats every 2 seconds when connected
setInterval(async () => {
if (isConnected) {
await updateStatus();
}
}, 2000);
});
async function loadServers() {
try {
const servers = await invoke('fetch_servers');
const select = document.getElementById('serverSelect');
select.innerHTML = '';
servers.forEach(server => {
const option = document.createElement('option');
option.value = server;
option.textContent = server;
select.appendChild(option);
});
} catch (error) {
console.error('Failed to load servers:', error);
showError('Failed to load server list');
}
}
async function toggleConnection() {
const button = document.getElementById('connectButton');
const buttonText = document.getElementById('buttonText');
button.disabled = true;
buttonText.textContent = 'Connecting...';
try {
if (!isConnected) {
const response = await invoke('connect_vpn');
console.log('Connect response:', response);
isConnected = true;
updateUI(true);
} else {
const response = await invoke('disconnect_vpn');
console.log('Disconnect response:', response);
isConnected = false;
updateUI(false);
}
} catch (error) {
console.error('Connection error:', error);
showError(error);
} finally {
button.disabled = false;
}
}
function updateUI(connected) {
const button = document.getElementById('connectButton');
const buttonText = document.getElementById('buttonText');
const statusIndicator = document.getElementById('statusIndicator');
const statsGrid = document.getElementById('statsGrid');
if (connected) {
button.classList.add('connected');
buttonText.textContent = 'Disconnect';
statusIndicator.textContent = 'Connected';
statusIndicator.classList.add('connected');
statsGrid.style.display = 'grid';
} else {
button.classList.remove('connected');
buttonText.textContent = 'Connect';
statusIndicator.textContent = 'Disconnected';
statusIndicator.classList.remove('connected');
statsGrid.style.display = 'none';
}
}
async function updateStatus() {
if (!isConnected) return;
try {
const status = await invoke('get_status');
const data = JSON.parse(status);
// Update stats
document.getElementById('uploadSpeed').textContent = formatSpeed(data.upload_speed);
document.getElementById('downloadSpeed').textContent = formatSpeed(data.download_speed);
document.getElementById('ping').textContent = `${data.ping} ms`;
} catch (error) {
console.error('Failed to update status:', error);
}
}
function formatSpeed(bytesPerSecond) {
if (bytesPerSecond < 1024) {
return `${bytesPerSecond} B/s`;
} else if (bytesPerSecond < 1024 * 1024) {
return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
} else {
return `${(bytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`;
}
}
function showSettings() {
alert('Settings panel coming soon!');
}
function showError(message) {
// TODO: Better error UI
alert(`Error: ${message}`);
}

55
ostp-gui/ui/index.html Normal file
View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OSTP VPN</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<div class="header">
<h1>OSTP VPN</h1>
<div class="status-indicator" id="statusIndicator">Disconnected</div>
</div>
<div class="main-content">
<!-- Connection Toggle -->
<button class="connect-button" id="connectButton" onclick="toggleConnection()">
<span id="buttonText">Connect</span>
</button>
<!-- Server Selection -->
<div class="server-section">
<label for="serverSelect">Server:</label>
<select id="serverSelect">
<option value="">Loading...</option>
</select>
</div>
<!-- Stats Display -->
<div class="stats-grid" id="statsGrid" style="display: none;">
<div class="stat">
<div class="stat-label">Upload</div>
<div class="stat-value" id="uploadSpeed">0 KB/s</div>
</div>
<div class="stat">
<div class="stat-label">Download</div>
<div class="stat-value" id="downloadSpeed">0 KB/s</div>
</div>
<div class="stat">
<div class="stat-label">Ping</div>
<div class="stat-value" id="ping">0 ms</div>
</div>
</div>
<!-- Settings -->
<div class="settings-section">
<button class="settings-button" onclick="showSettings()">⚙ Settings</button>
</div>
</div>
</div>
<script src="app.js"></script>
</body>
</html>

162
ostp-gui/ui/styles.css Normal file
View File

@@ -0,0 +1,162 @@
/* Dark Stealth Theme */
:root {
--bg-dark: #0a0a0a;
--bg-darker: #050505;
--accent: #00ff88;
--accent-dim: #00aa55;
--text: #ffffff;
--text-dim: #888888;
--border: #222222;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: var(--bg-dark);
color: var(--text);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.container {
width: 100%;
height: 100%;
background: var(--bg-darker);
display: flex;
flex-direction: column;
}
.header {
padding: 20px;
border-bottom: 1px solid var(--border);
text-align: center;
}
.header h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 10px;
}
.status-indicator {
display: inline-block;
padding: 5px 15px;
border-radius: 20px;
font-size: 12px;
background: var(--border);
color: var(--text-dim);
}
.status-indicator.connected {
background: var(--accent);
color: var(--bg-dark);
font-weight: 600;
}
.main-content {
flex: 1;
padding: 30px;
display: flex;
flex-direction: column;
gap: 25px;
}
.connect-button {
width: 100%;
height: 80px;
border: 3px solid var(--accent-dim);
background: transparent;
color: var(--accent);
font-size: 20px;
font-weight: 600;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
}
.connect-button:hover {
background: var(--accent-dim);
color: var(--bg-dark);
}
.connect-button.connected {
background: var(--accent);
color: var(--bg-dark);
border-color: var(--accent);
}
.server-section {
display: flex;
flex-direction: column;
gap: 10px;
}
.server-section label {
font-size: 14px;
color: var(--text-dim);
}
.server-section select {
padding: 12px;
background: var(--bg-dark);
color: var(--text);
border: 1px solid var(--border);
border-radius: 5px;
font-size: 14px;
cursor: pointer;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
.stat {
background: var(--bg-dark);
padding: 15px;
border-radius: 8px;
text-align: center;
}
.stat-label {
font-size: 11px;
color: var(--text-dim);
margin-bottom: 5px;
text-transform: uppercase;
}
.stat-value {
font-size: 18px;
font-weight: 600;
color: var(--accent);
}
.settings-section {
margin-top: auto;
}
.settings-button {
width: 100%;
padding: 12px;
background: transparent;
color: var(--text-dim);
border: 1px solid var(--border);
border-radius: 5px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.settings-button:hover {
border-color: var(--accent-dim);
color: var(--accent);
}