refactor: reduce window size and remove transfer metrics display from main UI

Reduce default window dimensions from 1120x760 to 940x640 pixels and disable resizing. Remove TunnelMetrics and RawTunnelMetrics types, formatDataSize and normalizeTunnelMetrics helpers, and all transfer statistics tracking from App component. Replace refreshTunnelMetrics with simpler refreshTunnelStatus that only queries tunnel active state. Remove received/sent data display cards from status panel and eliminate metrics
This commit is contained in:
2026-03-18 10:34:12 +01:00
parent 799bc6550e
commit 56acc96229
3 changed files with 37 additions and 96 deletions

View File

@@ -13,8 +13,8 @@
"windows": [
{
"title": "NexaVPN",
"width": 1120,
"height": 760,
"width": 940,
"height": 640,
"resizable": false,
"maximizable": false
}

View File

@@ -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,35 +53,21 @@ export function App() {
const [error, setError] = useState<string | null>(null);
const [connected, setConnected] = useState(false);
const [state, setState] = useState<EnrollmentState | null>(null);
const [metrics, setMetrics] = useState<TunnelMetrics>({
active: false,
rxBytes: 0,
txBytes: 0
});
async function refreshTunnelMetrics() {
try {
const value = normalizeTunnelMetrics(await invoke<RawTunnelMetrics>("tunnel_metrics"));
setMetrics(value);
setConnected(value.active);
} catch {
async function refreshTunnelStatus() {
try {
const active = await invoke<boolean>("tunnel_status");
setConnected(active);
setMetrics((current) => ({ ...current, active: Boolean(active) }));
} catch {
setMetrics({ active: false, rxBytes: 0, txBytes: 0 });
setConnected(false);
}
}
}
useEffect(() => {
void invoke<EnrollmentState | null>("load_state")
.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<boolean>("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<boolean>("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<EnrollmentState>("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() {
<span>Last sync</span>
<strong>{state.lastSyncTime}</strong>
</div>
<div className="detail-card">
<span>Received</span>
<strong>{formatDataSize(metrics.rxBytes)}</strong>
</div>
<div className="detail-card">
<span>Sent</span>
<strong>{formatDataSize(metrics.txBytes)}</strong>
</div>
</div>
</div>
@@ -365,7 +299,7 @@ export function App() {
<h4>Resources</h4>
</div>
</div>
<p>Press <strong>Sync</strong> after policy changes.</p>
<p className="access-hint">Press <strong>Sync</strong> after policy changes.</p>
<ul className="resource-stack">
{(state?.resources ?? ["No resources assigned yet"]).map((resource) => (
<li key={resource}>{resource}</li>

View File

@@ -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 {