init
This commit is contained in:
61
internal/app/web/templates/access.html
Normal file
61
internal/app/web/templates/access.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{{define "access.html"}}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
{{template "partials_head" .}}
|
||||
<body>
|
||||
{{template "partials_nav" .}}
|
||||
<main class="page">
|
||||
<h1>Access</h1>
|
||||
{{template "partials_flash" .}}
|
||||
|
||||
<section class="card">
|
||||
<h2>Grant</h2>
|
||||
<form method="post" action="{{abs "/access"}}">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<input type="hidden" name="action" value="grant">
|
||||
<div class="grid">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<select name="username" required>
|
||||
{{range .Users}}<option value="{{.Username}}">{{.Username}}</option>{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Topic / Pattern</label>
|
||||
<input name="topic" placeholder="alerts_* oder mytopic" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Permission</label>
|
||||
<select name="perm" required>
|
||||
<option value="read-write">read-write</option>
|
||||
<option value="read-only">read-only</option>
|
||||
<option value="write-only">write-only</option>
|
||||
<option value="deny">deny</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn" type="submit">Setzen</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Reset</h2>
|
||||
<form method="post" action="{{abs "/access"}}" onsubmit="return confirm('Access wirklich resetten?')">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<input type="hidden" name="action" value="reset">
|
||||
<label>User</label>
|
||||
<select name="username" required>
|
||||
{{range .Users}}<option value="{{.Username}}">{{.Username}}</option>{{end}}
|
||||
</select>
|
||||
<button class="btn btn-danger" type="submit">Reset</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div class="hint">
|
||||
Hinweis: Access-Kontrolle basiert auf <code>ntfy access</code> und benötigt eine korrekt konfigurierte <code>auth-file</code> im <code>server.yml</code>.
|
||||
</div>
|
||||
</main>
|
||||
{{template "partials_footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
105
internal/app/web/templates/admins.html
Normal file
105
internal/app/web/templates/admins.html
Normal file
@@ -0,0 +1,105 @@
|
||||
{{define "admins.html"}}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
{{template "partials_head" .}}
|
||||
<body>
|
||||
{{template "partials_nav" .}}
|
||||
<main class="page">
|
||||
<h1>WebUI Admins</h1>
|
||||
{{template "partials_flash" .}}
|
||||
|
||||
<section class="card">
|
||||
<h2>Neuen Admin erstellen</h2>
|
||||
<form method="post" action="{{abs "/admins"}}">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<input type="hidden" name="action" value="create">
|
||||
<div class="grid">
|
||||
<div><label>Username</label><input name="username" required></div>
|
||||
<div><label>Passwort</label><input name="password" type="password" required></div>
|
||||
<div>
|
||||
<label>Rolle</label>
|
||||
<select name="role">
|
||||
<option value="viewer">viewer</option>
|
||||
<option value="operator" selected>operator</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn" type="submit">Erstellen</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Liste</h2>
|
||||
<table class="table">
|
||||
<thead><tr><th>User</th><th>Role</th><th>2FA</th><th>Status</th><th>Aktionen</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Admins}}
|
||||
<tr>
|
||||
<td>{{.Username}}</td>
|
||||
<td>{{.Role}}</td>
|
||||
<td>{{if .TOTPSecret}}<span class="pill">enabled</span>{{else}}<span class="pill pill-muted">off</span>{{end}}</td>
|
||||
<td>{{if .Disabled}}<span class="pill pill-muted">disabled</span>{{else}}<span class="pill">active</span>{{end}}</td>
|
||||
<td>
|
||||
<form class="inline" method="post" action="{{abs "/admins"}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="action" value="set-role">
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
<select name="role">
|
||||
<option value="viewer">viewer</option>
|
||||
<option value="operator">operator</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
<button class="btn btn-ghost" type="submit">Set role</button>
|
||||
</form>
|
||||
|
||||
<form class="inline" method="post" action="{{abs "/admins"}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="action" value="set-pass">
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
<input name="password" type="password" placeholder="new password">
|
||||
<button class="btn btn-ghost" type="submit">Set pass</button>
|
||||
</form>
|
||||
|
||||
<form class="inline" method="post" action="{{abs "/admins"}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="action" value="toggle-disable">
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
<button class="btn btn-ghost" type="submit">Toggle</button>
|
||||
</form>
|
||||
|
||||
{{if .TOTPSecret}}
|
||||
<form class="inline" method="post" action="{{abs "/admins"}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="action" value="2fa-disable">
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
<button class="btn btn-ghost" type="submit">2FA off</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<form class="inline" method="post" action="{{abs "/admins"}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="action" value="2fa-enable">
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
<button class="btn btn-ghost" type="submit">2FA on</button>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
<form class="inline" method="post" action="{{abs "/admins"}}" onsubmit="return confirm('Admin löschen?')">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
<button class="btn btn-danger" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="hint">2FA Secret wird als Flash angezeigt. In einer Authenticator-App als TOTP-Secret (base32) hinzufügen.</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
{{template "partials_footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
31
internal/app/web/templates/audit.html
Normal file
31
internal/app/web/templates/audit.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{{define "audit.html"}}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
{{template "partials_head" .}}
|
||||
<body>
|
||||
{{template "partials_nav" .}}
|
||||
<main class="page">
|
||||
<h1>Audit Log (letzte 200)</h1>
|
||||
{{template "partials_flash" .}}
|
||||
|
||||
<section class="card">
|
||||
<table class="table">
|
||||
<thead><tr><th>Time</th><th>Actor</th><th>IP</th><th>Action</th><th>Target</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Audit}}
|
||||
<tr>
|
||||
<td><code class="mono">{{.Time}}</code></td>
|
||||
<td>{{.Actor}}</td>
|
||||
<td><code class="mono">{{.IP}}</code></td>
|
||||
<td>{{.Action}}</td>
|
||||
<td>{{.Target}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</main>
|
||||
{{template "partials_footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
18
internal/app/web/templates/error.html
Normal file
18
internal/app/web/templates/error.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{define "error.html"}}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
{{template "partials_head" .}}
|
||||
<body>
|
||||
{{template "partials_nav" .}}
|
||||
<main class="page">
|
||||
<h1>Fehler</h1>
|
||||
{{template "partials_flash" .}}
|
||||
<section class="card">
|
||||
<pre class="pre">{{.Error}}</pre>
|
||||
<div class="hint">Tipp: In Docker sicherstellen, dass <code>/etc/ntfy/server.yml</code> und <code>/var/lib/ntfy</code> korrekt gemountet sind.</div>
|
||||
</section>
|
||||
</main>
|
||||
{{template "partials_footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
38
internal/app/web/templates/layout.html
Normal file
38
internal/app/web/templates/layout.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{{define "partials_head"}}
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{if .Title}}{{.Title}} – {{end}}ntfywui</title>
|
||||
<link rel="stylesheet" href="{{abs "/static/app.css"}}">
|
||||
</head>
|
||||
{{end}}
|
||||
|
||||
{{define "partials_nav"}}
|
||||
<nav class="nav">
|
||||
<div class="brand"><a href="{{abs "/users"}}">ntfywui</a></div>
|
||||
{{if .Admin}}
|
||||
<div class="links">
|
||||
<a href="{{abs "/users"}}">Users</a>
|
||||
{{if or (eq .Role "operator") (eq .Role "admin")}}<a href="{{abs "/access"}}">Access</a>{{end}}
|
||||
{{if or (eq .Role "operator") (eq .Role "admin")}}<a href="{{abs "/tokens"}}">Tokens</a>{{end}}
|
||||
{{if eq .Role "admin"}}<a href="{{abs "/admins"}}">Admins</a>{{end}}
|
||||
{{if eq .Role "admin"}}<a href="{{abs "/audit"}}">Audit</a>{{end}}
|
||||
</div>
|
||||
<div class="user">
|
||||
<span class="badge">{{.Admin}} ({{.Role}})</span>
|
||||
<a class="btn btn-ghost" href="{{abs "/logout"}}">Logout</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</nav>
|
||||
{{end}}
|
||||
|
||||
{{define "partials_flash"}}
|
||||
{{if .Flash}}<div class="flash">{{.Flash}}</div>{{end}}
|
||||
{{if .Error}}<div class="flash flash-err">{{.Error}}</div>{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "partials_footer"}}
|
||||
<footer class="footer">
|
||||
<div>ntfywui – Webverwaltung für ntfy (CLI-basiert). Standardbibliothek-only.</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
29
internal/app/web/templates/login.html
Normal file
29
internal/app/web/templates/login.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{{define "login.html"}}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
{{template "partials_head" .}}
|
||||
<body>
|
||||
<div class="page center">
|
||||
<div class="card">
|
||||
<h1>Login</h1>
|
||||
{{template "partials_flash" .}}
|
||||
<form method="post" action="{{abs "/login"}}">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<input type="hidden" name="next" value="{{.Next}}">
|
||||
<label>Username</label>
|
||||
<input name="username" autocomplete="username" required>
|
||||
<label>Password</label>
|
||||
<input name="password" type="password" autocomplete="current-password" required>
|
||||
<label>TOTP (optional)</label>
|
||||
<input name="totp" inputmode="numeric" autocomplete="one-time-code" placeholder="123456">
|
||||
<button class="btn" type="submit">Anmelden</button>
|
||||
</form>
|
||||
|
||||
<div class="hint">
|
||||
Tipp: Setze <code>NTFYWUI_BOOTSTRAP_USER</code> und <code>NTFYWUI_BOOTSTRAP_PASS</code> für den ersten Admin.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
40
internal/app/web/templates/tokens.html
Normal file
40
internal/app/web/templates/tokens.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{{define "tokens.html"}}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
{{template "partials_head" .}}
|
||||
<body>
|
||||
{{template "partials_nav" .}}
|
||||
<main class="page">
|
||||
<h1>Tokens</h1>
|
||||
{{template "partials_flash" .}}
|
||||
|
||||
<section class="card">
|
||||
<h2>Token erstellen</h2>
|
||||
<form method="post" action="{{abs "/tokens"}}">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<div class="grid">
|
||||
<div>
|
||||
<label>User</label>
|
||||
<select name="username" required>
|
||||
{{range .Users}}<option value="{{.Username}}">{{.Username}}</option>{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Label (optional)</label>
|
||||
<input name="label">
|
||||
</div>
|
||||
<div>
|
||||
<label>Expires (optional)</label>
|
||||
<input name="expires" placeholder="120d, 24h, ...">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn" type="submit">Create</button>
|
||||
</form>
|
||||
<div class="hint">Token wird als Flash angezeigt, danach nicht mehr.</div>
|
||||
</section>
|
||||
</main>
|
||||
{{template "partials_footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
102
internal/app/web/templates/user.html
Normal file
102
internal/app/web/templates/user.html
Normal file
@@ -0,0 +1,102 @@
|
||||
{{define "user.html"}}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
{{template "partials_head" .}}
|
||||
<body>
|
||||
{{template "partials_nav" .}}
|
||||
<main class="page">
|
||||
{{template "partials_flash" .}}
|
||||
|
||||
{{with .User}}{{ $uname := .Username }}
|
||||
<h1>User: {{.Username}}</h1>
|
||||
|
||||
<section class="card">
|
||||
<h2>Details</h2>
|
||||
<div class="grid">
|
||||
<div><span class="muted">Role</span><div>{{.Role}}</div></div>
|
||||
<div><span class="muted">Tier</span><div>{{.Tier}}</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{if or (eq $.Role "operator") (eq $.Role "admin")}}
|
||||
<section class="card">
|
||||
<h2>Aktionen</h2>
|
||||
|
||||
<div class="grid">
|
||||
<form method="post" action="{{abs (print "/users/" .Username "/password")}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<label>Neues Passwort</label>
|
||||
<input name="password" type="password" required>
|
||||
<button class="btn" type="submit">Passwort ändern</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="{{abs (print "/users/" .Username "/role")}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<label>Role</label>
|
||||
<select name="role">
|
||||
<option value="user">user</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
<button class="btn" type="submit">Rolle setzen</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="{{abs (print "/users/" .Username "/tier")}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<label>Tier</label>
|
||||
<input name="tier" placeholder="none/pro/...">
|
||||
<button class="btn" type="submit">Tier setzen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{abs (print "/users/" .Username "/delete")}}" onsubmit="return confirm('User wirklich löschen?')">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<button class="btn btn-danger" type="submit">User löschen</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Access</h2>
|
||||
{{range .Access}}<div class="pill">{{.Perm}} → {{.Topic}}</div>{{end}}
|
||||
<div class="hint">Access wird mit <code>ntfy access</code> verwaltet (siehe Access-Seite).</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Tokens</h2>
|
||||
{{if $.Tokens}}
|
||||
{{range $.Tokens}}
|
||||
<div class="row">
|
||||
<code class="mono">{{.Token}}</code>
|
||||
<form method="post" action="{{abs "/tokens"}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="action" value="remove">
|
||||
<input type="hidden" name="username" value="{{$uname}}">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<button class="btn btn-ghost" type="submit">Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div class="muted">Keine Tokens (oder nicht auslesbar).</div>
|
||||
{{end}}
|
||||
|
||||
<h3>Token hinzufügen</h3>
|
||||
<form method="post" action="{{abs "/tokens"}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
<div class="grid">
|
||||
<div><label>Label (optional)</label><input name="label"></div>
|
||||
<div><label>Expires (optional)</label><input name="expires" placeholder="120d, 24h, ..."></div>
|
||||
</div>
|
||||
<button class="btn" type="submit">Token erstellen</button>
|
||||
<div class="hint">Der Token wird nur einmal als Flash angezeigt – direkt kopieren.</div>
|
||||
</form>
|
||||
</section>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
</main>
|
||||
{{template "partials_footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
65
internal/app/web/templates/users.html
Normal file
65
internal/app/web/templates/users.html
Normal file
@@ -0,0 +1,65 @@
|
||||
{{define "users.html"}}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
{{template "partials_head" .}}
|
||||
<body>
|
||||
{{template "partials_nav" .}}
|
||||
<main class="page">
|
||||
<h1>Users</h1>
|
||||
{{template "partials_flash" .}}
|
||||
|
||||
{{if or (eq .Role "operator") (eq .Role "admin")}}
|
||||
<section class="card">
|
||||
<h2>Neuen ntfy User erstellen</h2>
|
||||
<form method="post" action="{{abs "/users"}}">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<input type="hidden" name="action" value="create">
|
||||
<div class="grid">
|
||||
<div>
|
||||
<label>Username</label>
|
||||
<input name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Passwort</label>
|
||||
<input name="password" type="password" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Rolle</label>
|
||||
<select name="role">
|
||||
<option value="user">user</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Tier (optional)</label>
|
||||
<input name="tier" placeholder="none/pro/...">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn" type="submit">Erstellen</button>
|
||||
</form>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<section class="card">
|
||||
<h2>Liste</h2>
|
||||
<table class="table">
|
||||
<thead><tr><th>User</th><th>Role</th><th>Tier</th><th>Access</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Users}}
|
||||
<tr>
|
||||
<td><a href="{{abs (print "/users/" .Username)}}">{{.Username}}</a></td>
|
||||
<td>{{.Role}}</td>
|
||||
<td>{{.Tier}}</td>
|
||||
<td>
|
||||
{{range .Access}}<span class="pill">{{.Perm}} → {{.Topic}}</span> {{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</main>
|
||||
{{template "partials_footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user