- 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>
60 lines
2.2 KiB
HTML
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> | 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>
|