1635190797
The v0.1 GUI's Connect button was broken in practice: the Tauri app launched from /Applications runs as the desktop user, so `Command::new(aura).spawn()` started aura without root. aura died in ms with EPERM at TUN creation, faster than the 1.5 s status poller could catch — the UI just silently flipped back to "disconnected" with no clue. ## Fix * `cli_proc::spawn_client` now prepends `sudo -n` on Unix. After spawn it blocks for 1.5 s and checks `try_wait`; if the child already exited, it reads the stderr ring's last 20 lines and returns an anyhow Error with that tail + a hint list of common causes. The Tauri command surfaces it to the frontend's `error` state where the UI renders it as a multi-line `<pre>` block instead of the previous single-line text. * `ClientHandle::kill` no longer uses `Child::kill` (SIGKILL) on its sudo parent — that would have left aura orphaned with the TUN lingering. Sends SIGTERM to sudo, which sudo forwards to aura, giving the inner `OsRouteGuard::Drop` 2 s to run cleanup. Falls back to SIGKILL only after the grace period. ## One-click NOPASSWD installer Two new Tauri commands plus a UI banner: * `check_admin_access` — runs `sudo -n aura --help` and returns whether the sudoers entry is in place. Used by the React side to decide whether to show the banner. * `install_sudoers_admin` — runs `osascript ... with administrator privileges` which surfaces the native macOS auth dialog, then writes `/etc/sudoers.d/aura-gui` scoped to `<aura> client *` only (not arbitrary aura invocations), runs `visudo -c` for syntax validation, and reports success or the syntax error. The frontend shows a yellow "One-time setup needed" banner above the profile list whenever `adminReady === false`. Clicking the button pops the Mac password dialog once; from then on Connect is a single click with no prompt. ## UI feedback * "Connecting…" disabled state on the Connect button while spawn_client's 1.5 s wait is in progress * Errors render as monospace `<pre>` so the multi-line stderr tail is readable * `.error` and `.admin-banner` CSS classes added to App.css 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
313 lines
5.1 KiB
CSS
313 lines
5.1 KiB
CSS
/* AuraVPN GUI — dark-mode by default, dense single-pane VPN dashboard. */
|
|
|
|
:root {
|
|
--bg: #0f1115;
|
|
--panel-bg: #1a1d24;
|
|
--border: #2a2f3a;
|
|
--text: #d9dde4;
|
|
--text-dim: #8a92a3;
|
|
--accent: #5ad3aa;
|
|
--accent-hot: #4ac09a;
|
|
--danger: #ef5a5a;
|
|
--warn: #f7b955;
|
|
--bad: #ef5a5a;
|
|
|
|
font-family: -apple-system, "Segoe UI", Inter, Avenir, Helvetica, Arial,
|
|
sans-serif;
|
|
font-size: 14px;
|
|
line-height: 1.45;
|
|
color: var(--text);
|
|
background-color: var(--bg);
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
}
|
|
|
|
.container {
|
|
max-width: 760px;
|
|
margin: 0 auto;
|
|
padding: 24px 24px 48px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
header {
|
|
padding-bottom: 8px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
header h1 {
|
|
margin: 0;
|
|
font-size: 28px;
|
|
font-weight: 600;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
header .sub {
|
|
margin: 4px 0 0;
|
|
color: var(--text-dim);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.pill {
|
|
display: inline-block;
|
|
padding: 2px 8px;
|
|
border-radius: 999px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
.pill.running {
|
|
background: rgba(90, 211, 170, 0.18);
|
|
color: var(--accent);
|
|
}
|
|
|
|
.pill.stopped {
|
|
background: rgba(138, 146, 163, 0.18);
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.panel {
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 16px;
|
|
}
|
|
|
|
.panel.small {
|
|
padding: 10px 14px;
|
|
}
|
|
|
|
.panel h2 {
|
|
margin: 0 0 12px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
color: var(--text-dim);
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.row-between {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.row-between h2 {
|
|
margin: 0;
|
|
}
|
|
|
|
.empty {
|
|
margin: 12px 0;
|
|
color: var(--text-dim);
|
|
font-style: italic;
|
|
}
|
|
|
|
button {
|
|
background: #2a2f3a;
|
|
color: var(--text);
|
|
border: 1px solid #2a2f3a;
|
|
border-radius: 6px;
|
|
padding: 6px 12px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: background 0.15s, border-color 0.15s;
|
|
}
|
|
|
|
button:hover:not(:disabled) {
|
|
background: #353a45;
|
|
border-color: #404552;
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
button.primary {
|
|
background: var(--accent);
|
|
color: #0f1115;
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
button.primary:hover:not(:disabled) {
|
|
background: var(--accent-hot);
|
|
border-color: var(--accent-hot);
|
|
}
|
|
|
|
button.danger {
|
|
background: var(--danger);
|
|
color: white;
|
|
border-color: var(--danger);
|
|
}
|
|
|
|
button.danger:hover:not(:disabled) {
|
|
background: #d44e4e;
|
|
border-color: #d44e4e;
|
|
}
|
|
|
|
.profile-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.profile-list li {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 12px 14px;
|
|
background: #14171d;
|
|
border: 1px solid #232730;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.profile-list li.active {
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 1px rgba(90, 211, 170, 0.4);
|
|
}
|
|
|
|
.profile-meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.profile-name {
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.profile-server {
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
}
|
|
|
|
.profile-id {
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.profile-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.badge {
|
|
font-size: 10px;
|
|
font-weight: 500;
|
|
padding: 1px 6px;
|
|
border-radius: 4px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.badge.bad {
|
|
background: rgba(239, 90, 90, 0.15);
|
|
color: var(--bad);
|
|
}
|
|
|
|
.status {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
}
|
|
|
|
.status td {
|
|
padding: 6px 8px;
|
|
border-bottom: 1px solid #232730;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.status td:first-child {
|
|
width: 40%;
|
|
color: var(--text-dim);
|
|
text-transform: uppercase;
|
|
font-size: 11px;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
.status td.warn {
|
|
color: var(--warn);
|
|
}
|
|
|
|
.logs {
|
|
background: #0a0c10;
|
|
border: 1px solid #232730;
|
|
border-radius: 6px;
|
|
padding: 10px 12px;
|
|
font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
font-size: 11px;
|
|
max-height: 260px;
|
|
overflow: auto;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
color: #c6cbd5;
|
|
}
|
|
|
|
.error {
|
|
background: rgba(239, 90, 90, 0.12);
|
|
border: 1px solid rgba(239, 90, 90, 0.4);
|
|
border-radius: 8px;
|
|
padding: 12px 14px;
|
|
color: #ffb1b1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.error-body {
|
|
margin: 0;
|
|
font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
font-size: 11px;
|
|
color: #ffc8c8;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
max-height: 200px;
|
|
overflow: auto;
|
|
}
|
|
|
|
.error button {
|
|
align-self: flex-end;
|
|
}
|
|
|
|
.admin-banner {
|
|
background: rgba(247, 185, 85, 0.12);
|
|
border: 1px solid rgba(247, 185, 85, 0.4);
|
|
border-radius: 8px;
|
|
padding: 14px 16px;
|
|
color: #f7b955;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.admin-banner > div {
|
|
flex: 1;
|
|
}
|
|
|
|
.admin-banner button {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.aura-bin code {
|
|
font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
}
|