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:
117
ostp-gui/ui/app.js
Normal file
117
ostp-gui/ui/app.js
Normal 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
55
ostp-gui/ui/index.html
Normal 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
162
ostp-gui/ui/styles.css
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user