feat: add logout functionality and redesign desktop client UI for simplified workflow

Add clear_session command to remove session state and profile files from disk. Add resetEnrollment handler in frontend to clear local state and invoke clear_session. Remove hero surface section with profile metadata tiles. Simplify top strip to show profile label in brand copy when enrolled. Add Logout button to top actions and resources sidebar. Redesign status panel with simplified labels and layout. Update surface
This commit is contained in:
2026-03-17 21:34:53 +01:00
parent 464dca0795
commit 0986a36aca
3 changed files with 65 additions and 91 deletions

View File

@@ -103,6 +103,17 @@ export function App() {
}
}
async function resetEnrollment() {
try {
await invoke("clear_session");
setConnected(false);
setState(null);
setError(null);
} catch (err) {
setError(formatInvokeError(err, "Unable to clear local profile"));
}
}
return (
<div className="client-shell">
<div className="app-frame">
@@ -111,14 +122,19 @@ export function App() {
<img src="/icon.png" alt="NexaVPN" />
<div className="brand-copy">
<p className="eyebrow">NexaVPN</p>
<h1>Desktop Access Client</h1>
<p>Provisioned WireGuard access with profile sync and one-click reconnect.</p>
<h1>VPN Client</h1>
<p>{state ? profileLabel : "Sign in to provision this device"}</p>
</div>
</div>
<div className="top-actions">
{state ? (
<button className="shell-button-secondary" disabled={syncing} onClick={onSyncProfile} type="button">
{syncing ? "Syncing..." : "Sync profile"}
{syncing ? "Syncing..." : "Sync"}
</button>
) : null}
{state ? (
<button className="shell-button-secondary" onClick={resetEnrollment} type="button">
Logout
</button>
) : null}
<button className="shell-button" disabled={!state} onClick={toggleConnection} type="button">
@@ -127,34 +143,13 @@ export function App() {
</div>
</div>
<section className="hero-surface">
<div className="hero-copy">
<p className="eyebrow">Managed Tunnel</p>
<h2>Private access with fewer moving parts.</h2>
<p>
NexaVPN signs you in, enrolls the device, stores the WireGuard profile locally, and lets you resync the
assigned access profile after policy changes.
</p>
</div>
<div className="hero-meta">
<div className="meta-tile">
<span className="eyebrow">Current profile</span>
<strong>{profileLabel}</strong>
</div>
<div className="meta-tile">
<span className="eyebrow">Last sync</span>
<strong>{state?.lastSyncTime ?? "Not synced yet"}</strong>
</div>
</div>
</section>
<div className="body-grid">
{!state ? (
<section className="login-panel">
<div className="surface-header">
<div>
<p className="eyebrow">Onboarding</p>
<h3>Sign in and provision this device</h3>
<p className="eyebrow">Sign in</p>
<h3>Provision device</h3>
</div>
</div>
<form onSubmit={onSubmit}>
@@ -182,11 +177,11 @@ export function App() {
<section className="status-panel">
<div className="status-top">
<div>
<p className="eyebrow">Connection</p>
<p className="eyebrow">Status</p>
<h3>{connected ? "Connected" : "Disconnected"}</h3>
<div className="status-state">
<span className={`status-dot ${connected ? "online" : ""}`} />
{connected ? "Tunnel active" : "Ready to connect"}
{connected ? "Tunnel active" : "Ready"}
</div>
</div>
</div>
@@ -194,8 +189,8 @@ export function App() {
<div className="surface">
<div className="surface-header">
<div>
<p className="eyebrow">Session</p>
<h4>Provisioned device state</h4>
<p className="eyebrow">Connection</p>
<h4>Current profile</h4>
</div>
</div>
<div className="status-grid">
@@ -208,8 +203,8 @@ export function App() {
<strong>{state.gatewayEndpoint}</strong>
</div>
<div className="detail-card">
<span>Profile revision</span>
<strong>{state.profileRevision}</strong>
<span>Profile</span>
<strong>{profileLabel}</strong>
</div>
<div className="detail-card">
<span>Last sync</span>
@@ -221,8 +216,8 @@ export function App() {
<div className="surface">
<div className="surface-header">
<div>
<p className="eyebrow">Profile</p>
<h4>Assigned access profile</h4>
<p className="eyebrow">Local</p>
<h4>Stored config</h4>
</div>
</div>
<div className="profile-grid">
@@ -231,8 +226,8 @@ export function App() {
<strong>{state.profilePath}</strong>
</div>
<div className="profile-card">
<span>Tunnel strategy</span>
<strong>{state.tunnelStrategy}</strong>
<span>Revision</span>
<strong>{state.profileRevision}</strong>
</div>
</div>
</div>
@@ -244,19 +239,21 @@ export function App() {
<aside className="status-panel">
<div className="surface-header">
<div>
<p className="eyebrow">Resources</p>
<h4>Allowed destinations</h4>
<p className="eyebrow">Access</p>
<h4>Resources</h4>
</div>
</div>
<p>
The current backend issues one effective profile per device. After policy changes, use <strong>Sync
profile</strong> to pull the latest assigned access.
</p>
<p>After policy changes, press <strong>Sync</strong> to refresh this device profile.</p>
<ul className="resource-list">
{(state?.resources ?? ["No resources assigned yet"]).map((resource) => (
<li key={resource}>{resource}</li>
))}
</ul>
{state ? (
<button className="shell-button-secondary" onClick={resetEnrollment} type="button">
Neu einbinden
</button>
) : null}
</aside>
</div>
</div>