feat: add resilient updates and access controls
Container-Image veröffentlichen / container (push) Has been cancelled
Container-Image veröffentlichen / container (push) Has been cancelled
This commit is contained in:
1 parent
a000152423
commit
f02ec9977c
41 files changed
+3277
-151
No files matched your search
@@ -32,7 +32,7 @@
|
||||
<form action="/targets/{{.ID}}/save" method="post" class="target-form" data-target-form data-ipv6-enabled="{{$.Settings.IPv6Enabled}}" data-update-ipv4="{{.UpdateIPv4}}" data-update-ipv6="{{.UpdateIPv6}}">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<div class="toggle-row"><label class="switch"><input type="checkbox" name="active" data-target-active {{if .Active}}checked{{end}}><span></span> DynDNS aktiv</label>{{if $.IsAdmin}}<label class="switch"><input type="checkbox" name="update_ipv4" {{if .UpdateIPv4}}checked{{end}}><span></span> IPv4 / A</label><label class="switch {{if not $.Settings.IPv6Enabled}}switch-disabled{{end}}"><input type="checkbox" name="update_ipv6" {{if and .UpdateIPv6 $.Settings.IPv6Enabled}}checked{{end}} {{if not $.Settings.IPv6Enabled}}disabled{{end}}><span></span> IPv6 / AAAA{{if not $.Settings.IPv6Enabled}} pausiert{{end}}</label>{{end}}</div>
|
||||
<div class="form-grid" data-static-ip-fields {{if .Active}}hidden{{end}} aria-hidden="{{if .Active}}true{{else}}false{{end}}"><label>Feste IPv4 <input name="static_ipv4" data-static-ipv4 value="{{.StaticIPv4}}" inputmode="decimal" placeholder="203.0.113.10" {{if .Active}}disabled{{else if .UpdateIPv4}}required{{else}}disabled{{end}}><small>Pflichtfeld, wenn IPv4 aktiv und DynDNS aus ist.</small></label><label class="{{if not $.Settings.IPv6Enabled}}switch-disabled{{end}}">Feste IPv6 <input name="static_ipv6" data-static-ipv6 value="{{.StaticIPv6}}" inputmode="text" placeholder="2001:db8::10" {{if or .Active (not $.Settings.IPv6Enabled)}}disabled{{else if .UpdateIPv6}}required{{else}}disabled{{end}}><small>Pflichtfeld, wenn IPv6 aktiv und DynDNS aus ist.</small></label></div>
|
||||
<div class="form-grid" data-static-ip-fields {{if .Active}}hidden{{end}} aria-hidden="{{if .Active}}true{{else}}false{{end}}"><label>Feste IPv4 <input name="static_ipv4" data-static-ipv4 data-current-ipv4-placeholder value="{{.StaticIPv4}}" inputmode="decimal" placeholder="{{if $.Settings.LastIPv4}}{{$.Settings.LastIPv4}}{{else}}203.0.113.10{{end}}" {{if .Active}}disabled{{else if .UpdateIPv4}}required{{else}}disabled{{end}}><small>Pflichtfeld, wenn IPv4 aktiv und DynDNS aus ist; grau steht die zuletzt ermittelte aktuelle IPv4.</small></label><label class="{{if not $.Settings.IPv6Enabled}}switch-disabled{{end}}">Feste IPv6 <input name="static_ipv6" data-static-ipv6 value="{{.StaticIPv6}}" inputmode="text" placeholder="2001:db8::10" {{if or .Active (not $.Settings.IPv6Enabled)}}disabled{{else if .UpdateIPv6}}required{{else}}disabled{{end}}><small>Pflichtfeld, wenn IPv6 aktiv und DynDNS aus ist.</small></label></div>
|
||||
<p class="target-requirement" data-target-requirement hidden>Trage für jede aktive IP-Familie eine feste Adresse ein oder aktiviere DynDNS.</p>
|
||||
<div class="form-grid"><label>Beschreibung <input name="description" value="{{.Description}}" maxlength="512" placeholder="Interne Beschreibung"></label><label>TTL <select name="ttl" required>{{if not (supportedTTL $target.TTL)}}<option value="{{$target.TTL}}" selected>{{$target.TTL}} Sekunden (bestehend)</option>{{end}}{{range ttlOptions}}<option value="{{.}}" {{if eq . $target.TTL}}selected{{end}}>{{.}} Sekunden</option>{{end}}</select></label></div>
|
||||
<div class="form-actions"><button class="button button-primary" type="submit">Speichern</button></div>
|
||||
@@ -106,7 +106,7 @@
|
||||
{{if or .IsAdmin .CanCreateTargets}}
|
||||
<section class="action-grid admin-actions-grid">
|
||||
<article class="panel"><div class="panel-heading"><div><span class="section-label">Record anlegen</span><h2>Neue Subdomain</h2></div></div>
|
||||
<form action="/targets" method="post" class="stacked-form" data-target-form data-ipv6-enabled="{{.Settings.IPv6Enabled}}" data-update-ipv4="true" data-update-ipv6="false"><input type="hidden" name="csrf" value="{{.CSRF}}"><input type="hidden" name="zone_id" value="{{.Zone.ID}}"><label>{{if .IsAdmin}}Name oder Root-Record{{else}}Subdomain{{end}} <input name="record_name" required placeholder="{{if .IsAdmin}}home oder @{{else}}home{{end}}"><small>Die Zone {{.Zone.Name}} wird automatisch ergänzt.</small></label><div class="toggle-row"><label class="switch"><input type="checkbox" name="active" data-target-active checked><span></span> DynDNS aktivieren</label><label class="switch"><input type="checkbox" name="update_ipv4" checked><span></span> IPv4</label><label class="switch {{if not .Settings.IPv6Enabled}}switch-disabled{{end}}"><input type="checkbox" name="update_ipv6" {{if not .Settings.IPv6Enabled}}disabled{{end}}><span></span> IPv6{{if not .Settings.IPv6Enabled}} pausiert{{end}}</label></div><div class="form-grid" data-static-ip-fields hidden aria-hidden="true"><label>Feste IPv4 <input name="static_ipv4" data-static-ipv4 inputmode="decimal" placeholder="203.0.113.10" disabled><small>Pflichtfeld, wenn IPv4 aktiv und DynDNS aus ist.</small></label><label class="{{if not .Settings.IPv6Enabled}}switch-disabled{{end}}">Feste IPv6 <input name="static_ipv6" data-static-ipv6 inputmode="text" placeholder="2001:db8::10" disabled><small>Pflichtfeld, wenn IPv6 aktiv und DynDNS aus ist.</small></label></div><p class="target-requirement" data-target-requirement hidden>Trage für jede aktive IP-Familie eine feste Adresse ein oder aktiviere DynDNS.</p><div class="form-grid"><label>Beschreibung <input name="description" maxlength="512" placeholder="Interne Beschreibung"></label><label>TTL <select name="ttl" required>{{range ttlOptions}}<option value="{{.}}" {{if eq . 60}}selected{{end}}>{{.}} Sekunden</option>{{end}}</select></label></div><button class="button button-primary" type="submit">Record anlegen</button></form>
|
||||
<form action="/targets" method="post" class="stacked-form" data-target-form data-ipv6-enabled="{{.Settings.IPv6Enabled}}" data-update-ipv4="true" data-update-ipv6="false"><input type="hidden" name="csrf" value="{{.CSRF}}"><input type="hidden" name="zone_id" value="{{.Zone.ID}}"><label>{{if .IsAdmin}}Name oder Root-Record{{else}}Subdomain{{end}} <input name="record_name" required placeholder="{{if .IsAdmin}}home oder @{{else}}home{{end}}"><small>Die Zone {{.Zone.Name}} wird automatisch ergänzt.</small></label><div class="toggle-row"><label class="switch"><input type="checkbox" name="active" data-target-active checked><span></span> DynDNS aktivieren</label><label class="switch"><input type="checkbox" name="update_ipv4" checked><span></span> IPv4</label><label class="switch {{if not .Settings.IPv6Enabled}}switch-disabled{{end}}"><input type="checkbox" name="update_ipv6" {{if not .Settings.IPv6Enabled}}disabled{{end}}><span></span> IPv6{{if not .Settings.IPv6Enabled}} pausiert{{end}}</label></div><div class="form-grid" data-static-ip-fields hidden aria-hidden="true"><label>Feste IPv4 <input name="static_ipv4" data-static-ipv4 data-current-ipv4-placeholder inputmode="decimal" placeholder="{{if .Settings.LastIPv4}}{{.Settings.LastIPv4}}{{else}}203.0.113.10{{end}}" disabled><small>Pflichtfeld, wenn IPv4 aktiv und DynDNS aus ist; grau steht die zuletzt ermittelte aktuelle IPv4.</small></label><label class="{{if not .Settings.IPv6Enabled}}switch-disabled{{end}}">Feste IPv6 <input name="static_ipv6" data-static-ipv6 inputmode="text" placeholder="2001:db8::10" disabled><small>Pflichtfeld, wenn IPv6 aktiv und DynDNS aus ist.</small></label></div><p class="target-requirement" data-target-requirement hidden>Trage für jede aktive IP-Familie eine feste Adresse ein oder aktiviere DynDNS.</p><div class="form-grid"><label>Beschreibung <input name="description" maxlength="512" placeholder="Interne Beschreibung"></label><label>TTL <select name="ttl" required>{{range ttlOptions}}<option value="{{.}}" {{if eq . 60}}selected{{end}}>{{.}} Sekunden</option>{{end}}</select></label></div><button class="button button-primary" type="submit">Record anlegen</button></form>
|
||||
</article>
|
||||
{{if .IsAdmin}}
|
||||
<article class="panel"><div class="panel-heading"><div><span class="section-label">Statischer Record</span><h2>Neuen CNAME anlegen</h2></div></div>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light">
|
||||
<title>{{.Title}} · DNS Control</title>
|
||||
<link rel="stylesheet" href="/static/styles.css?v=features-15">
|
||||
<script src="/static/app.js?v=features-15" defer></script>
|
||||
<link rel="stylesheet" href="/static/styles.css?v=features-21">
|
||||
<script src="/static/app.js?v=features-21" defer></script>
|
||||
</head>
|
||||
<body class="page-{{.Page}}">
|
||||
<a class="skip-link" href="#main">Zum Inhalt springen</a>
|
||||
@@ -26,13 +26,12 @@
|
||||
<a href="/pihole" {{if eq .Page "pihole"}}aria-current="page"{{end}}><span class="nav-icon">03</span>Pi-hole</a>
|
||||
<a href="/settings" {{if eq .Page "settings"}}aria-current="page"{{end}}><span class="nav-icon">04</span>Updater</a>
|
||||
<a href="/errors" {{if eq .Page "errors"}}aria-current="page"{{end}}><span class="nav-icon">05</span>Fehler</a>
|
||||
<a href="/users" {{if eq .Page "users"}}aria-current="page"{{end}}><span class="nav-icon">06</span>Benutzer</a>
|
||||
<a href="/access" {{if eq .Page "access"}}aria-current="page"{{end}}><span class="nav-icon">06</span>Zugriff</a>
|
||||
<a href="/system" {{if eq .Page "system"}}aria-current="page"{{end}}><span class="nav-icon">07</span>Update{{if .SoftwareUpdate.UpdateAvailable}}<i class="nav-update-dot" aria-label="Update verfügbar"></i>{{end}}</a>
|
||||
{{end}}
|
||||
<a href="/security" {{if eq .Page "security"}}aria-current="page"{{end}}><span class="nav-icon">{{if .IsAdmin}}07{{else}}03{{end}}</span>Sicherheit</a>
|
||||
</nav>
|
||||
<div class="sidebar-account">
|
||||
<span class="account-avatar">{{initial .User.DisplayName}}</span>
|
||||
<span><strong>{{.User.DisplayName}}</strong><small>@{{.User.Username}} · {{if .IsAdmin}}Admin{{else}}Mitglied{{end}}</small></span>
|
||||
<a class="sidebar-account-link" href="/account" aria-label="Eigenes Konto öffnen"><span class="account-avatar">{{initial .User.DisplayName}}</span><span><strong>{{.User.DisplayName}}</strong><small>@{{.User.Username}} · {{if .IsAdmin}}Admin{{else}}Mitglied{{end}}</small></span></a>
|
||||
<form action="/logout" method="post">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<button type="submit" title="Abmelden" aria-label="Abmelden">↪</button>
|
||||
@@ -44,6 +43,16 @@
|
||||
<div><span>DNS Control</span><strong>/ {{.Title}}</strong></div>
|
||||
{{if eq .Mode "lan"}}<span class="environment-badge">LAN · HTTP</span>{{else}}<span class="environment-badge secure">HTTPS</span>{{end}}
|
||||
</header>
|
||||
{{if and .IsAdmin .SoftwareUpdate.UpdateAvailable (ne .Page "system")}}
|
||||
<aside class="software-update-banner" aria-label="Software-Update verfügbar">
|
||||
<span class="software-update-banner-icon" aria-hidden="true">↗</span>
|
||||
<div><span>Neue Version verfügbar</span><strong>{{.SoftwareUpdate.LatestVersion}} ist bereit</strong></div>
|
||||
<div class="software-update-banner-actions">
|
||||
<a class="button button-quiet" href="/system">Details</a>
|
||||
{{if .SoftwareUpdate.AgentAvailable}}<form action="/system/update" method="post" data-software-update-form><input type="hidden" name="csrf" value="{{.CSRF}}"><input type="hidden" name="version" value="{{.SoftwareUpdate.LatestVersion}}"><button class="button button-primary" type="submit">Jetzt aktualisieren</button></form>{{end}}
|
||||
</div>
|
||||
</aside>
|
||||
{{end}}
|
||||
<main id="main" class="app-shell">
|
||||
{{template "content" .}}
|
||||
</main>
|
||||
@@ -67,6 +76,18 @@
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .IsAdmin .SoftwareUpdate.UpdateAvailable (ne .Page "system")}}
|
||||
<div class="software-update-overlay" data-software-update-overlay hidden role="dialog" aria-modal="true" aria-labelledby="software-update-title-global">
|
||||
<div class="software-update-dialog">
|
||||
<div class="software-update-orbit" aria-hidden="true"><span>DC</span><i></i></div>
|
||||
<span class="section-label">Systemupdate</span><h2 id="software-update-title-global">Update wird vorbereitet</h2>
|
||||
<p data-software-update-message>Das neue Container-Image wird geladen.</p>
|
||||
<div class="software-update-progress"><i data-software-update-progress></i></div>
|
||||
<ol class="software-update-phases" aria-label="Update-Fortschritt"><li data-update-phase="pulling"><span>01</span>Image laden</li><li data-update-phase="restarting"><span>02</span>Dienst wechseln</li><li data-update-phase="verifying"><span>03</span>Healthcheck</li></ol>
|
||||
<button class="button button-quiet" type="button" data-software-update-close hidden>Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -1,18 +1,18 @@
|
||||
{{define "login"}}{{template "layout" .}}{{end}}
|
||||
{{define "content"}}
|
||||
<section class="auth-card">
|
||||
<div class="auth-brand"><span>DD</span> DynDNS Console</div>
|
||||
<h1>Anmelden</h1>
|
||||
<section class="auth-card login-card">
|
||||
<div class="login-card-top"><div class="auth-brand"><span>DD</span> DNS Control</div><span class="login-mode {{if eq .Mode "https"}}is-secure{{end}}">{{if eq .Mode "https"}}HTTPS{{else}}LAN{{end}}</span></div>
|
||||
<h1>Willkommen zurück</h1>
|
||||
<p class="lede">Melde dich mit deinem persönlichen Zugang an.</p>
|
||||
<form action="/login" method="post" class="stacked-form">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<label>Benutzername
|
||||
<input name="username" type="text" autocomplete="username" required autofocus>
|
||||
<input name="username" type="text" autocomplete="username" autocapitalize="none" spellcheck="false" maxlength="40" required autofocus>
|
||||
</label>
|
||||
<label>Passwort
|
||||
<input name="password" type="password" autocomplete="current-password" required>
|
||||
<input name="password" type="password" autocomplete="current-password" maxlength="128" required>
|
||||
</label>
|
||||
<button class="button button-primary" type="submit">Anmelden <span aria-hidden="true">→</span></button>
|
||||
</form>
|
||||
<div class="login-meta">Sitzungen laufen nach acht Stunden automatisch ab.</div>
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -1,8 +1,5 @@
|
||||
{{define "security"}}{{template "layout" .}}{{end}}
|
||||
{{define "content"}}
|
||||
<header class="page-heading"><div><span class="page-kicker">Konto und Laufzeit</span><h1>Sicherheit</h1><p>Eigenes Passwort ändern und aktive Sicherheitsmechanismen der Instanz prüfen.</p></div></header>
|
||||
<section class="split-grid">
|
||||
<article class="panel"><div class="panel-heading"><div><span class="section-label">Anmeldedaten</span><h2>Passwort ändern</h2></div></div><form action="/security/password" method="post" class="stacked-form"><input type="hidden" name="csrf" value="{{.CSRF}}"><label>Aktuelles Passwort <input type="password" name="current_password" autocomplete="current-password" required></label><label>Neues Passwort <input type="password" name="new_password" autocomplete="new-password" minlength="12" required></label><label>Neues Passwort wiederholen <input type="password" name="confirm_password" autocomplete="new-password" minlength="12" required></label><button class="button button-primary" type="submit">Passwort aktualisieren</button></form></article>
|
||||
<article class="panel security-facts"><div class="panel-heading"><div><span class="section-label">Instanzschutz</span><h2>Aktive Maßnahmen</h2></div></div><ul><li><strong>Argon2id</strong><span>Passwörter werden ausschließlich als gehärtete Hashes gespeichert.</span></li><li><strong>XChaCha20-Poly1305</strong><span>Hetzner-Tokens sind authentifiziert verschlüsselt.</span></li><li><strong>Rollen und Grants</strong><span>Mitglieder sehen nur explizit freigegebene Domains oder Hosts.</span></li><li><strong>CSRF und Sessions</strong><span>Schreibzugriffe sind tokengebunden; Sitzungen laufen automatisch ab.</span></li></ul>{{if eq .Mode "lan"}}<div class="warning-box">HTTP-LAN-Modus aktiv. Nur in einem isolierten, vertrauenswürdigen Netzwerk verwenden.</div>{{end}}</article>
|
||||
</section>
|
||||
<header class="page-heading"><div><span class="page-kicker">Persönlich</span><h1>Mein Konto</h1><p>@{{.User.Username}} · {{if .IsAdmin}}Administrator{{else}}Mitglied{{end}}</p></div>{{if .IsAdmin}}<a class="button button-quiet" href="/access">Zugriff verwalten</a>{{end}}</header>
|
||||
<section class="form-panel account-password-panel"><div class="panel-heading"><div><span class="section-label">Anmeldedaten</span><h2>Eigenes Passwort ändern</h2></div></div><form action="/account/password" method="post" class="stacked-form"><input type="hidden" name="csrf" value="{{.CSRF}}"><label>Aktuelles Passwort <input type="password" name="current_password" autocomplete="current-password" maxlength="128" required></label><div class="form-grid"><label>Neues Passwort <input type="password" name="new_password" autocomplete="new-password" minlength="15" maxlength="128" required></label><label>Passwort wiederholen <input type="password" name="confirm_password" autocomplete="new-password" minlength="15" maxlength="128" required></label></div><button class="button button-primary" type="submit">Passwort aktualisieren</button></form></section>
|
||||
{{end}}
|
||||
@@ -1,26 +1,64 @@
|
||||
{{define "setup"}}{{template "layout" .}}{{end}}
|
||||
{{define "content"}}
|
||||
<section class="auth-card">
|
||||
<div class="auth-brand"><span>DD</span> DynDNS Console</div>
|
||||
<h1>System einrichten</h1>
|
||||
<p class="lede">Erstelle den ersten Administrator. Weitere Benutzer und Zugriffsrechte können anschließend in der Verwaltung angelegt werden.</p>
|
||||
<form action="/setup" method="post" class="stacked-form">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<div class="form-grid"><label>Admin-Benutzername
|
||||
<input name="username" type="text" value="{{index .Form "username"}}" autocomplete="username" minlength="3" maxlength="40" required autofocus>
|
||||
</label>
|
||||
<label>Anzeigename
|
||||
<input name="display_name" type="text" value="{{index .Form "display_name"}}" maxlength="80" required>
|
||||
</label></div>
|
||||
<label>Admin-Passwort
|
||||
<input name="password" type="password" autocomplete="new-password" minlength="12" required>
|
||||
<small>Mindestens 12 Zeichen; dieses Passwort kann in V1 nicht über die Oberfläche zurückgesetzt werden.</small>
|
||||
</label>
|
||||
<label>Passwort wiederholen
|
||||
<input name="confirm_password" type="password" autocomplete="new-password" minlength="12" required>
|
||||
</label>
|
||||
<button class="button button-primary" type="submit">Sichere Einrichtung abschließen <span aria-hidden="true">→</span></button>
|
||||
</form>
|
||||
<div class="security-strip"><span>✓</span> Argon2id-Passworthash · verschlüsselte API-Tokens · lokale Datenhaltung</div>
|
||||
<section class="onboarding-shell">
|
||||
<aside class="onboarding-intro">
|
||||
<div class="onboarding-brand-row">
|
||||
<div class="auth-brand auth-brand-inverse"><span>DD</span> DNS Control</div>
|
||||
<span class="onboarding-edition">Self-hosted</span>
|
||||
</div>
|
||||
<div class="onboarding-copy">
|
||||
<span class="onboarding-eyebrow">Erster Start</span>
|
||||
<h1>DNS-Verwaltung,<br>die einfach läuft.</h1>
|
||||
<p>Einrichten, verbinden, automatisch aktuell halten.</p>
|
||||
</div>
|
||||
<div class="onboarding-progress" aria-label="Einrichtung: Schritt 1 von 3">
|
||||
<div><span>Einrichtung</span><strong>1 von 3</strong></div>
|
||||
<span class="onboarding-progress-track" aria-hidden="true"><i></i></span>
|
||||
</div>
|
||||
<ol class="onboarding-steps" aria-label="Einrichtung">
|
||||
<li class="is-current" aria-current="step"><span>01</span><div><div class="onboarding-step-title"><strong>Administrator anlegen</strong><em>Aktuell</em></div><small>Sicheren Zugang für diese Instanz erstellen.</small></div></li>
|
||||
<li><span>02</span><div><strong>Dienste verbinden</strong><small>Hetzner und optional Pi-hole hinzufügen.</small></div></li>
|
||||
<li><span>03</span><div><strong>Automatisieren</strong><small>DNS-Einträge zuverlässig aktuell halten.</small></div></li>
|
||||
</ol>
|
||||
</aside>
|
||||
|
||||
<section class="onboarding-card" aria-labelledby="setup-title">
|
||||
<div class="onboarding-form-wrap">
|
||||
<div class="onboarding-card-meta">
|
||||
<span class="setup-ready"><i aria-hidden="true"></i> Instanz bereit</span>
|
||||
<span>Schritt 1 von 3</span>
|
||||
</div>
|
||||
<header class="onboarding-form-heading">
|
||||
<h2 id="setup-title">Administrator anlegen</h2>
|
||||
<p class="onboarding-lede">Lege den ersten Administrator für diese Instanz an.</p>
|
||||
</header>
|
||||
|
||||
<form action="/setup" method="post" class="stacked-form setup-form" data-setup-form>
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<div class="form-grid setup-field-row"><label>Benutzername
|
||||
<input name="username" type="text" value="{{index .Form "username"}}" autocomplete="username" autocapitalize="none" spellcheck="false" minlength="3" maxlength="40" required autofocus>
|
||||
</label>
|
||||
<label>Anzeigename
|
||||
<input name="display_name" type="text" value="{{index .Form "display_name"}}" autocomplete="name" maxlength="80" required>
|
||||
</label></div>
|
||||
<div class="form-grid setup-field-row"><label>Admin-Passphrase
|
||||
<input name="password" type="password" autocomplete="new-password" minlength="15" maxlength="128" aria-describedby="password-guidance" data-setup-password required>
|
||||
</label>
|
||||
<label>Passphrase wiederholen
|
||||
<input name="confirm_password" type="password" autocomplete="new-password" minlength="15" maxlength="128" data-setup-confirm required>
|
||||
</label></div>
|
||||
<div class="password-guidance" id="password-guidance" aria-live="polite">
|
||||
<span data-password-length><i aria-hidden="true">✓</i> Mindestens 15 Zeichen</span>
|
||||
<span data-password-match><i aria-hidden="true">✓</i> Eingaben stimmen überein</span>
|
||||
</div>
|
||||
<button class="button button-primary setup-submit" type="submit">Administrator erstellen <span aria-hidden="true">→</span></button>
|
||||
</form>
|
||||
|
||||
<div class="onboarding-footer onboarding-mode {{if eq .Mode "https"}}is-secure{{end}}">
|
||||
<span aria-hidden="true">{{if eq .Mode "https"}}●{{else}}!{{end}}</span>
|
||||
<p><strong>{{if eq .Mode "https"}}HTTPS-Modus aktiv{{else}}LAN-Modus aktiv{{end}}</strong><small>{{if eq .Mode "https"}}Verschlüsselte Verbindung{{else}}Nur im lokalen Netz verwenden{{end}}</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -0,0 +1,58 @@
|
||||
{{define "system"}}{{template "layout" .}}{{end}}
|
||||
{{define "content"}}
|
||||
<header class="page-heading system-heading">
|
||||
<div><span class="page-kicker">Wartung</span><h1>Software-Update</h1><p>Version prüfen und die Instanz kontrolliert aktualisieren.</p></div>
|
||||
<form action="/system/check" method="post"><input type="hidden" name="csrf" value="{{.CSRF}}"><button class="button button-quiet software-check-button" type="submit"><i aria-hidden="true">↻</i> Nach Update suchen</button></form>
|
||||
</header>
|
||||
|
||||
<section class="software-version-card {{if .SoftwareUpdate.UpdateAvailable}}has-update{{end}}">
|
||||
<div class="software-version-visual" aria-hidden="true"><span>DC</span><i></i></div>
|
||||
<div class="software-version-copy">
|
||||
<div class="software-version-values">
|
||||
<div class="software-version-cell"><span>Installiert</span><strong>{{.SoftwareUpdate.CurrentVersion}}</strong></div>
|
||||
<div class="software-version-cell is-latest"><span>Verfügbar</span><strong>{{if .SoftwareUpdate.LatestVersion}}{{.SoftwareUpdate.LatestVersion}}{{else}}–{{end}}</strong></div>
|
||||
</div>
|
||||
<p class="software-version-state">{{if .SoftwareUpdate.UpdateAvailable}}Eine neue stabile Version ist verfügbar.{{else if .SoftwareUpdate.LastError}}Die letzte Prüfung ist fehlgeschlagen; der Dienst läuft normal weiter.{{else if .SoftwareUpdate.LastCheckedAt}}Kein neues Update verfügbar.{{else}}Noch nicht nach Updates gesucht.{{end}}</p>
|
||||
<div class="software-version-meta"><span>Letzte Prüfung <b>{{formatTime .SoftwareUpdate.LastCheckedAt}}</b></span><span>Update-Agent <b>{{if .SoftwareUpdate.AgentAvailable}}bereit{{else}}nicht erreichbar{{end}}</b></span></div>
|
||||
{{if .SoftwareUpdate.LastError}}<div class="update-inline-warning"><span>!</span><p><strong>Prüfung fehlgeschlagen</strong><small>{{.SoftwareUpdate.LastError}}</small></p></div>{{end}}
|
||||
</div>
|
||||
<div class="software-version-action">
|
||||
{{if and .SoftwareUpdate.UpdateAvailable .SoftwareUpdate.AgentAvailable}}
|
||||
<form action="/system/update" method="post" data-software-update-form>
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}"><input type="hidden" name="version" value="{{.SoftwareUpdate.LatestVersion}}">
|
||||
<button class="button button-primary software-update-button" type="submit"><span>Jetzt aktualisieren</span><i aria-hidden="true">↗</i></button>
|
||||
</form>
|
||||
{{else if .SoftwareUpdate.UpdateAvailable}}
|
||||
<span class="status-pill status-warn">Update-Agent nicht erreichbar</span>
|
||||
{{else}}
|
||||
<span class="status-pill {{if or .SoftwareUpdate.LastError (not .SoftwareUpdate.CredentialConfigured)}}status-warn{{else}}status-good{{end}}">{{if not .SoftwareUpdate.CredentialConfigured}}Zugang fehlt{{else if .SoftwareUpdate.LastError}}Prüfung fehlgeschlagen{{else if .SoftwareUpdate.LastCheckedAt}}Aktuell{{else}}Bereit{{end}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel update-credential-panel" id="update-access">
|
||||
<div class="panel-heading"><div><span class="section-label">Zugang</span><h2>{{if .SoftwareUpdate.CredentialConfigured}}Update-Zugang ersetzen{{else}}Update-Zugang einrichten{{end}}</h2></div>{{if .SoftwareUpdate.CredentialConfigured}}<span class="status-pill status-good">verschlüsselt</span>{{end}}</div>
|
||||
<form action="/system/update-credential" method="post" class="stacked-form">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<div class="form-grid"><label>Gitea-Benutzername <input name="registry_username" autocomplete="username" maxlength="128" required></label><label>Token <input name="registry_token" type="password" autocomplete="new-password" minlength="8" maxlength="1024" required></label></div>
|
||||
<label>Admin-Passwort bestätigen <input name="current_password" type="password" autocomplete="current-password" maxlength="128" required></label>
|
||||
<div class="form-actions"><button class="button button-primary" type="submit">Zugang sicher speichern</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div class="software-update-overlay" data-software-update-overlay hidden role="dialog" aria-modal="true" aria-labelledby="software-update-title">
|
||||
<div class="software-update-dialog">
|
||||
<div class="software-update-orbit" aria-hidden="true"><span>DC</span><i></i></div>
|
||||
<span class="section-label">Systemupdate</span>
|
||||
<h2 id="software-update-title">Update wird vorbereitet</h2>
|
||||
<p data-software-update-message>Das neue Container-Image wird geladen. DNS-Daten und Secrets bleiben erhalten.</p>
|
||||
<div class="software-update-progress"><i data-software-update-progress></i></div>
|
||||
<ol class="software-update-phases" aria-label="Update-Fortschritt">
|
||||
<li data-update-phase="pulling"><span>01</span>Image laden</li>
|
||||
<li data-update-phase="restarting"><span>02</span>Dienst wechseln</li>
|
||||
<li data-update-phase="verifying"><span>03</span>Healthcheck</li>
|
||||
</ol>
|
||||
<button class="button button-quiet" type="button" data-software-update-close hidden>Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -2,7 +2,7 @@
|
||||
{{define "content"}}
|
||||
<header class="page-heading">
|
||||
<div>
|
||||
<a class="back-link" href="/users">← Benutzer</a>
|
||||
<a class="back-link" href="/access">← Zugriff & Sicherheit</a>
|
||||
<span class="page-kicker">Benutzerprofil</span>
|
||||
<h1>{{.ManagedUser.DisplayName}}</h1>
|
||||
<p>@{{.ManagedUser.Username}} · {{if eq .ManagedUser.Role "admin"}}Admin mit vollständigem Zugriff{{else}}Mitglied mit gezielten Freigaben{{end}}</p>
|
||||
@@ -25,8 +25,8 @@
|
||||
<div class="panel-heading"><div><span class="section-label">Anmeldedaten</span><h2>Passwort zurücksetzen</h2></div></div>
|
||||
<form action="/users/{{.ManagedUser.ID}}/password" method="post" class="stacked-form">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<label>Neues Passwort <input name="password" type="password" minlength="12" autocomplete="new-password" required></label>
|
||||
<label>Passwort wiederholen <input name="confirm_password" type="password" minlength="12" autocomplete="new-password" required></label>
|
||||
<label>Neues Passwort <input name="password" type="password" minlength="15" maxlength="128" autocomplete="new-password" required></label>
|
||||
<label>Passwort wiederholen <input name="confirm_password" type="password" minlength="15" maxlength="128" autocomplete="new-password" required></label>
|
||||
<button class="button button-danger" type="submit">Passwort zurücksetzen</button>
|
||||
<small>Alle Sitzungen dieses Benutzers werden dabei beendet.</small>
|
||||
</form>
|
||||
@@ -68,6 +68,6 @@
|
||||
</form>
|
||||
</section>
|
||||
{{else}}
|
||||
<section class="panel access-panel"><div class="warning-box">Dieser Benutzer ist Admin und hat deshalb automatisch vollen Zugriff auf alle Domains, Hosts, Tokens und Einstellungen.</div></section>
|
||||
<section class="admin-full-access-card access-panel"><span class="admin-full-access-icon" aria-hidden="true">A</span><div><span class="section-label">Administratorrolle</span><h2>Vollzugriff ist fest mit der Rolle verbunden</h2><p>Für Administratoren gibt es keine bearbeitbaren Domain- oder Host-Einzelrechte.</p></div><span class="status-pill status-good">Alle Rechte</span></section>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -1,13 +1,13 @@
|
||||
{{define "user_new"}}{{template "layout" .}}{{end}}
|
||||
{{define "content"}}
|
||||
<header class="page-heading"><div><a class="back-link" href="/users">← Benutzer</a><span class="page-kicker">Neues Konto</span><h1>Benutzer anlegen</h1><p>Mitgliedskonten erhalten zunächst keine Domain- oder Host-Berechtigungen.</p></div></header>
|
||||
<section class="form-panel compact-panel">
|
||||
<header class="page-heading"><div><a class="back-link" href="/access">← Zugriff & Sicherheit</a><span class="page-kicker">Neues Konto</span><h1>Benutzer anlegen</h1><p>Mitgliedskonten starten ohne Freigaben.</p></div></header>
|
||||
<section class="form-panel compact-panel user-create-panel">
|
||||
<form action="/users" method="post" class="stacked-form wide-form">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<div class="form-grid"><label>Benutzername <input name="username" value="{{index .Form "username"}}" autocomplete="username" minlength="3" maxlength="40" required><small>Kleinbuchstaben, Zahlen, Punkt, Bindestrich und Unterstrich.</small></label><label>Anzeigename <input name="display_name" value="{{index .Form "display_name"}}" maxlength="80" required></label></div>
|
||||
<div class="form-grid"><label>Startpasswort <input name="password" type="password" autocomplete="new-password" minlength="12" required></label><label>Passwort wiederholen <input name="confirm_password" type="password" autocomplete="new-password" minlength="12" required></label></div>
|
||||
<div class="form-grid user-create-grid user-create-identity-grid"><label>Benutzername <input name="username" value="{{index .Form "username"}}" autocomplete="username" minlength="3" maxlength="40" required><small>Kleinbuchstaben, Zahlen, Punkt, Bindestrich und Unterstrich.</small></label><label>Anzeigename <input name="display_name" value="{{index .Form "display_name"}}" maxlength="80" required><small class="field-spacer" aria-hidden="true"> </small></label></div>
|
||||
<div class="form-grid user-create-grid"><label>Startpasswort <input name="password" type="password" autocomplete="new-password" maxlength="128" required></label><label>Passwort wiederholen <input name="confirm_password" type="password" autocomplete="new-password" maxlength="128" required></label></div>
|
||||
<label>Rolle <select name="role"><option value="member" {{if eq (index .Form "role") "member"}}selected{{end}}>Mitglied – nur explizit freigegebene Domains/Hosts</option><option value="admin" {{if eq (index .Form "role") "admin"}}selected{{end}}>Admin – vollständige Verwaltung</option></select><small>Admins dürfen Tokens sehen, Einstellungen ändern und weitere Benutzer verwalten.</small></label>
|
||||
<div class="form-actions"><button class="button button-primary" type="submit">Benutzer anlegen</button><a class="button button-quiet" href="/users">Abbrechen</a></div>
|
||||
<div class="form-actions"><button class="button button-primary" type="submit">Benutzer anlegen</button><a class="button button-quiet" href="/access">Abbrechen</a></div>
|
||||
</form>
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -1,5 +1,9 @@
|
||||
{{define "users"}}{{template "layout" .}}{{end}}
|
||||
{{define "content"}}
|
||||
<header class="page-heading"><div><span class="page-kicker">Zugriffssteuerung</span><h1>Benutzer</h1><p>Konten, Rollen sowie Domain- und Subdomain-Berechtigungen verwalten.</p></div><a class="button button-primary" href="/users/new">Benutzer anlegen</a></header>
|
||||
<header class="page-heading"><div><span class="page-kicker">Administration</span><h1>Zugriff & Sicherheit</h1><p>Konten, Rollen und Freigaben an einem Ort.</p></div><div class="heading-actions"><a class="button button-quiet" href="/account">Mein Konto</a><a class="button button-primary" href="/users/new">Benutzer anlegen</a></div></header>
|
||||
<section class="access-policy-grid" aria-label="Rollenmodell">
|
||||
<article class="access-policy-card is-admin"><span class="access-policy-icon" aria-hidden="true">A</span><div><strong>Administratoren</strong><small>Vollzugriff ohne bearbeitbare Einzelrechte</small></div><span class="status-pill status-good">Vollzugriff</span></article>
|
||||
<article class="access-policy-card"><span class="access-policy-icon" aria-hidden="true">M</span><div><strong>Mitglieder</strong><small>Nur ausdrücklich zugewiesene Domains und Hosts</small></div><span class="status-pill">Freigaben</span></article>
|
||||
</section>
|
||||
<section class="panel user-table-panel"><div class="panel-heading"><div><span class="section-label">Konten</span><h2>{{len .Users}} Benutzer</h2></div></div><div class="user-table" role="table" aria-label="Benutzer"><div class="user-table-head" role="row"><span>Benutzer</span><span>Rolle</span><span>Status</span><span>Letzte Anmeldung</span><span></span></div>{{range .Users}}<a href="/users/{{.ID}}" class="user-table-row" role="row"><span><strong>{{.DisplayName}}</strong><small>@{{.Username}}</small></span><span class="role-chip {{.Role}}">{{if eq .Role "admin"}}Admin{{else}}Mitglied{{end}}</span><span class="status-pill {{if .Active}}status-good{{else}}status-bad{{end}}">{{if .Active}}aktiv{{else}}deaktiviert{{end}}</span><span class="muted-cell">{{formatTime .LastLoginAt}}</span><span class="row-arrow">→</span></a>{{end}}</div></section>
|
||||
{{end}}
|
||||
Reference in new issue
Block a user