helmd: inbäddad webbklient (vanilla SPA) + Docker-image för Pi5 (LAN-only)
All checks were successful
helmd-image / build (push) Successful in 36s
helmd-release / build-release (push) Successful in 1m6s

- web/: token-login, sessionslista, ANSI-skärmvy (SSE+poll), promptfält,
  frågekort, tangentrad, delningsflik (bild/HTML-sandbox/markdown), inställningar
- go:embed via webfs.go — servern förblir en enda binär, strikt CSP
- browser-verifierad mot riktig agy (fråga -> kort -> svar -> kört)
- Dockerfile (alpine + tmux/bash) + helmd-image.yaml -> localhost:5000/helmd
- integrationstest utökat till 29 gröna

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 22:17:12 +02:00
parent f9f94e59bc
commit 12ffcaa2bd
13 changed files with 830 additions and 6 deletions

110
helmd/web/index.html Normal file
View File

@@ -0,0 +1,110 @@
<!doctype html>
<html lang="sv">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#0b0e14">
<title>agent-helm</title>
<link rel="manifest" href="manifest.json">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='80' font-size='80'%3E⛵%3C/text%3E%3C/svg%3E">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="login" class="login hidden">
<div class="login-card">
<h1>⛵ agent-helm</h1>
<p>Klistra in API-token från servern (<code>helmd token</code>).</p>
<input id="tokenInput" type="password" placeholder="token" autocomplete="off">
<button id="tokenSave" class="primary">Anslut</button>
<p id="loginError" class="error"></p>
</div>
</div>
<div id="app" class="app hidden">
<header>
<span class="brand">⛵ agent-helm</span>
<nav>
<button data-tab="sessions" class="tab active">Sessioner</button>
<button data-tab="shares" class="tab">Delningar<span id="shareBadge" class="badge hidden"></span></button>
<button data-tab="settings" class="tab">Inställningar</button>
</nav>
<span id="connDot" class="dot" title="SSE-anslutning"></span>
</header>
<main id="tab-sessions" class="tab-page">
<aside class="sess-list">
<div class="sess-head">
<span>Sessioner</span>
<button id="newSessBtn" title="Ny session"></button>
</div>
<div id="sessItems"></div>
<form id="newSessForm" class="hidden">
<input id="newSessName" placeholder="namn (t.ex. agy)" required>
<input id="newSessCmd" placeholder="kommando (tomt = agent från config)">
<input id="newSessCwd" placeholder="arbetskatalog (tomt = default)">
<div class="row">
<button type="submit" class="primary">Starta</button>
<button type="button" id="newSessCancel">Avbryt</button>
</div>
</form>
</aside>
<section class="sess-view">
<div id="noSess" class="empty">Ingen session vald — skapa en med </div>
<div id="sessPane" class="hidden">
<div class="sess-toolbar">
<span id="sessTitle"></span>
<span class="spacer"></span>
<button id="keyEsc" title="Escape">Esc</button>
<button id="keyUp" title="Pil upp"></button>
<button id="keyDown" title="Pil ned"></button>
<button id="keyEnter" title="Enter"></button>
<button id="keyCtrlC" title="Ctrl+C">^C</button>
<button id="modeBtn" title="Byt läge (Shift+Tab)">Läge</button>
<button id="killBtn" class="danger" title="Döda sessionen"></button>
</div>
<div id="questionCard" class="question hidden"></div>
<pre id="screen" class="screen"></pre>
<form id="promptForm">
<textarea id="promptText" rows="2" placeholder="Skriv prompt… (Enter skickar, Shift+Enter = ny rad)"></textarea>
<button type="submit" class="primary">Skicka</button>
</form>
</div>
</section>
</main>
<main id="tab-shares" class="tab-page hidden">
<div class="shares-wrap">
<div id="shareItems" class="share-list"></div>
<div id="shareView" class="share-view empty">Välj en delning i listan</div>
</div>
</main>
<main id="tab-settings" class="tab-page hidden">
<form id="cfgForm" class="cfg">
<h2>Agent</h2>
<label>Kommando <input id="cfgCmd"></label>
<label>Argument (mellanslagsseparerade) <input id="cfgArgs"></label>
<label>Arbetskatalog <input id="cfgWorkdir"></label>
<label>Poll-intervall (ms) <input id="cfgPoll" type="number" min="100" step="100"></label>
<h2>ntfy</h2>
<label class="check"><input id="cfgNtfyOn" type="checkbox"> ntfy aktiverat</label>
<label class="check"><input id="cfgNtfyQ" type="checkbox"> pusha vid ny fråga</label>
<label>Server <input id="cfgNtfyServer"></label>
<label>Topic <input id="cfgNtfyTopic"></label>
<div class="row">
<button type="submit" class="primary">Spara</button>
<span id="cfgStatus"></span>
</div>
<h2>Klient</h2>
<div class="row">
<button type="button" id="logoutBtn" class="danger">Glöm token (logga ut)</button>
<span id="verInfo" class="muted"></span>
</div>
</form>
</main>
</div>
<script type="module" src="app.js"></script>
</body>
</html>