warpgate/templates/web/layout.html
grabbit 6bb7ec4d27 Web UI overhaul: interactive config editor, SSE live updates, log viewer, and SMB reload fixes
- Replace raw TOML textarea with Alpine.js interactive form editor (10 collapsible
  sections with change-tier badges, dynamic array management for connections/shares/
  warmup rules, proper input controls per field type)
- Add SSE-based live dashboard updates replacing htmx polling
- Add log viewer tab with ring buffer backend and incremental polling
- Fix SMB not seeing new shares after config reload: kill entire smbd process group
  (not just parent PID) so forked workers release port 445
- Add SIGHUP-based smbd config reload for share changes instead of full restart,
  preserving existing client connections
- Generate human-readable commented TOML from config editor instead of bare
  toml::to_string_pretty() output
- Fix Alpine.js 2.x __x.$data calls in dashboard/share templates (now Alpine 3.x)
- Fix toggle switch CSS overlap with field labels
- Fix dashboard going blank on tab switch (remove hx-swap-oob from tab content)
- Add htmx:afterSettle → Alpine.initTree() bridge for robust tab switching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 18:06:52 +08:00

60 lines
2.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Warpgate Dashboard</title>
<link rel="stylesheet" href="/static/style.css">
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script src="https://unpkg.com/htmx-ext-sse@2.2.2/sse.js"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body hx-ext="sse" sse-connect="/events" x-data="{ activeTab: '{{ active_tab }}' }">
<div class="shell">
<div class="header">
<div>
<h1><span class="status-dot"></span>Warpgate</h1>
<div class="meta">Uptime: <span id="uptime">{{ uptime }}</span> &nbsp;|&nbsp; Config: {{ config_path }}</div>
</div>
</div>
<nav class="tabs">
<button class="tab-btn" :class="{ active: activeTab === 'dashboard' }"
@click="activeTab = 'dashboard'"
hx-get="/tabs/dashboard" hx-target="#tab-content" hx-swap="innerHTML">
Dashboard
</button>
<button class="tab-btn" :class="{ active: activeTab === 'shares' }"
@click="activeTab = 'shares'"
hx-get="/tabs/shares" hx-target="#tab-content" hx-swap="innerHTML">
Shares
</button>
<button class="tab-btn" :class="{ active: activeTab === 'config' }"
@click="activeTab = 'config'"
hx-get="/tabs/config" hx-target="#tab-content" hx-swap="innerHTML">
Config
</button>
<button class="tab-btn" :class="{ active: activeTab === 'logs' }"
@click="activeTab = 'logs'"
hx-get="/tabs/logs" hx-target="#tab-content" hx-swap="innerHTML">
Logs
</button>
</nav>
<div id="tab-content">
{{ tab_content }}
</div>
</div>
<!-- Hidden SSE listener: OOB swaps update regions inside dashboard tab -->
<div sse-swap="status" hx-swap="none" style="display:none"></div>
<script>
/* Alpine.js + htmx bridge: re-initialize Alpine trees after htmx content swaps */
document.body.addEventListener('htmx:afterSettle', function(evt) {
if (window.Alpine) Alpine.initTree(evt.detail.target || evt.detail.elt);
});
</script>
</body>
</html>