diff --git a/desktop-client/src-tauri/tauri.conf.json b/desktop-client/src-tauri/tauri.conf.json index 2a4d652..352e3e7 100644 --- a/desktop-client/src-tauri/tauri.conf.json +++ b/desktop-client/src-tauri/tauri.conf.json @@ -13,8 +13,8 @@ "windows": [ { "title": "NexaVPN", - "width": 1120, - "height": 760, + "width": 940, + "height": 640, "resizable": false, "maximizable": false } diff --git a/desktop-client/src/App.tsx b/desktop-client/src/App.tsx index 22becc2..8684925 100644 --- a/desktop-client/src/App.tsx +++ b/desktop-client/src/App.tsx @@ -11,20 +11,6 @@ type EnrollmentState = { tunnelStrategy: string; }; -type TunnelMetrics = { - active: boolean; - rxBytes: number; - txBytes: number; -}; - -type RawTunnelMetrics = { - active?: boolean; - rxBytes?: number; - txBytes?: number; - rx_bytes?: number; - tx_bytes?: number; -}; - function formatInvokeError(err: unknown, fallback: string) { if (typeof err === "string" && err.trim().length > 0) { return err; @@ -58,31 +44,6 @@ function currentProfileLabel(state: EnrollmentState | null) { return `Split tunnel (${state.resources.length} resources)`; } -function formatDataSize(bytes: number) { - if (!bytes) { - return "0 B"; - } - - const units = ["B", "KB", "MB", "GB", "TB"]; - let value = bytes; - let unitIndex = 0; - - while (value >= 1024 && unitIndex < units.length - 1) { - value /= 1024; - unitIndex += 1; - } - - return `${value >= 100 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unitIndex]}`; -} - -function normalizeTunnelMetrics(value: RawTunnelMetrics | null | undefined): TunnelMetrics { - return { - active: Boolean(value?.active), - rxBytes: Number(value?.rxBytes ?? value?.rx_bytes ?? 0), - txBytes: Number(value?.txBytes ?? value?.tx_bytes ?? 0) - }; -} - export function App() { const [serverUrl, setServerUrl] = useState("http://localhost"); const [username, setUsername] = useState(""); @@ -92,26 +53,12 @@ export function App() { const [error, setError] = useState(null); const [connected, setConnected] = useState(false); const [state, setState] = useState(null); - const [metrics, setMetrics] = useState({ - active: false, - rxBytes: 0, - txBytes: 0 - }); - - async function refreshTunnelMetrics() { + async function refreshTunnelStatus() { try { - const value = normalizeTunnelMetrics(await invoke("tunnel_metrics")); - setMetrics(value); - setConnected(value.active); + const active = await invoke("tunnel_status"); + setConnected(active); } catch { - try { - const active = await invoke("tunnel_status"); - setConnected(active); - setMetrics((current) => ({ ...current, active: Boolean(active) })); - } catch { - setMetrics({ active: false, rxBytes: 0, txBytes: 0 }); - setConnected(false); - } + setConnected(false); } } @@ -120,7 +67,7 @@ export function App() { .then(async (value) => { if (value) { setState(value); - await refreshTunnelMetrics(); + await refreshTunnelStatus(); } }) .catch(() => undefined); @@ -128,13 +75,13 @@ export function App() { useEffect(() => { if (!state) { - setMetrics({ active: false, rxBytes: 0, txBytes: 0 }); + setConnected(false); return undefined; } - void refreshTunnelMetrics(); + void refreshTunnelStatus(); const timer = window.setInterval(() => { - void refreshTunnelMetrics(); + void refreshTunnelStatus(); }, 5000); return () => window.clearInterval(timer); @@ -148,12 +95,10 @@ export function App() { const active = await invoke("tunnel_status"); setConnected(active); if (active === expected) { - await refreshTunnelMetrics(); return active; } } catch { if (!expected) { - setMetrics({ active: false, rxBytes: 0, txBytes: 0 }); setConnected(false); return false; } @@ -165,7 +110,6 @@ export function App() { return invoke("tunnel_status") .then(async (active) => { setConnected(active); - await refreshTunnelMetrics(); return active; }) .catch(() => false); @@ -181,7 +125,6 @@ export function App() { payload: { serverUrl, username, password } }); setState(result); - setMetrics({ active: false, rxBytes: 0, txBytes: 0 }); } catch (err) { setError(formatInvokeError(err, "Enrollment failed")); } finally { @@ -196,7 +139,7 @@ export function App() { try { const result = await invoke("sync_profile"); setState(result); - await refreshTunnelMetrics(); + await refreshTunnelStatus(); } catch (err) { setError(formatInvokeError(err, "Profile sync failed")); } finally { @@ -240,7 +183,6 @@ export function App() { await invoke("clear_session"); setConnected(false); setState(null); - setMetrics({ active: false, rxBytes: 0, txBytes: 0 }); setError(null); } catch (err) { setError(formatInvokeError(err, "Unable to clear local profile")); @@ -343,14 +285,6 @@ export function App() { Last sync {state.lastSyncTime} -
- Received - {formatDataSize(metrics.rxBytes)} -
-
- Sent - {formatDataSize(metrics.txBytes)} -
@@ -365,7 +299,7 @@ export function App() {

Resources

-

Press Sync after policy changes.

+

Press Sync after policy changes.

    {(state?.resources ?? ["No resources assigned yet"]).map((resource) => (
  • {resource}
  • diff --git a/desktop-client/src/styles.css b/desktop-client/src/styles.css index 28c59c5..957afc3 100644 --- a/desktop-client/src/styles.css +++ b/desktop-client/src/styles.css @@ -28,14 +28,14 @@ input { .client-shell { min-height: 100vh; - padding: 28px; + padding: 18px; } .app-frame { - width: min(1120px, 100%); + width: min(920px, 100%); margin: 0 auto; display: grid; - gap: 18px; + gap: 14px; } .top-strip { @@ -88,7 +88,7 @@ input { .top-actions { display: flex; - gap: 10px; + gap: 8px; flex-wrap: wrap; } @@ -96,7 +96,7 @@ input { .shell-button-secondary { border: 0; border-radius: 999px; - padding: 12px 18px; + padding: 10px 16px; font-weight: 700; cursor: pointer; transition: 160ms ease; @@ -130,16 +130,16 @@ input { .body-grid { display: grid; - grid-template-columns: 1.3fr 0.7fr; - gap: 22px; + grid-template-columns: minmax(0, 1fr) 290px; + gap: 16px; } .login-panel, .status-panel { border-radius: 28px; - padding: 24px; + padding: 18px; display: grid; - gap: 20px; + gap: 14px; } .status-panel > p { @@ -178,9 +178,9 @@ input { .surface { border-radius: 24px; - padding: 18px; + padding: 14px; display: grid; - gap: 14px; + gap: 12px; } .surface-header { @@ -199,22 +199,22 @@ input { .profile-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px; + gap: 10px; } .detail-card, .profile-card { - padding: 14px; - border-radius: 18px; + padding: 12px; + border-radius: 16px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(177, 197, 229, 0.1); display: grid; - gap: 6px; + gap: 5px; } .detail-card strong, .profile-card strong { - font-size: 1rem; + font-size: 0.98rem; word-break: break-word; } @@ -223,16 +223,23 @@ input { padding: 0; list-style: none; display: grid; - gap: 10px; + gap: 8px; } .resource-stack li { - padding: 14px 16px; + padding: 12px 14px; border-radius: 16px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(177, 197, 229, 0.1); color: #eef4ff; word-break: break-word; + min-height: 54px; + display: flex; + align-items: center; +} + +.access-hint { + font-size: 0.95rem; } .login-panel form {