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:
@@ -13,8 +13,8 @@
|
||||
"windows": [
|
||||
{
|
||||
"title": "NexaVPN",
|
||||
"width": 1120,
|
||||
"height": 760,
|
||||
"width": 940,
|
||||
"height": 640,
|
||||
"resizable": false,
|
||||
"maximizable": false
|
||||
}
|
||||
|
||||
@@ -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<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() {
|
||||
async function refreshTunnelStatus() {
|
||||
try {
|
||||
const value = normalizeTunnelMetrics(await invoke<RawTunnelMetrics>("tunnel_metrics"));
|
||||
setMetrics(value);
|
||||
setConnected(value.active);
|
||||
const active = await invoke<boolean>("tunnel_status");
|
||||
setConnected(active);
|
||||
} catch {
|
||||
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);
|
||||
}
|
||||
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<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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user