Compare commits

..
3 Commits
Author SHA1 Message Date
alientimandClaude Sonnet 5 0074e48e09 Responsive-Fixes per echtem Playwright/DevTools-Test verifiziert, zwei Bugs behoben
Auf Nachfrage tatsaechlich mit Browser-DevTools (Playwright/Chromium,
lokal installiert) statt nur per CSS-Analyse geprueft -- echte
Screenshots + automatisierte Overflow-Pruefung auf iPhone-16-Pro- (393x852),
iPad- (820x1180 Hoch-/1180x820 Querformat) und Desktop-Viewport (1440x900)
gegen die echte Live-Instanz auf Testsystem-Update-Restart.

Dabei zwei echte Bugs gefunden und behoben:

1. Topbar-Ueberlappung (ALLE Seiten, jeder eingeloggte Nutzer, jedes
   iPhone/kleine Handy): .topbar-left (Seitentitel) und .topbar-right
   (Pruef-Countdown- + DHCP-Pille) sind beide flex:1 ohne Zeilenumbruch --
   auf 393px Breite quetscht das den Titel auf eine Breite, die kleiner
   ist als sein eigener Inhalt braucht, wodurch der Titeltext sichtbar
   ueber seine Spalte hinauslief und mit der Pille kollidierte (per
   getBoundingClientRect nachgewiesen: 24.5px Ueberlauf). Fix: ab 560px
   Breite bricht die Topbar um, Titel und Pillen bekommen je eine eigene
   Zeile.

2. HTTP-/HTTPS-Port-Felder auf der NGINX-Seite stapelten trotz
   entsprechender Media-Query-Regel nicht auf Handy-Breite -- Ursache:
   inline style="flex:1" auf den .field-Divs hat Vorrang vor jeder externen
   @media-Regel, ganz gleich wie spezifisch. Fix: inline style entfernt,
   neue Klasse .field--half stattdessen -- die Mobile-Stapel-Regel greift
   jetzt wie vorgesehen.

Zusaetzlich EINE eigene Aenderung aus v1.0.9 wieder zurueckgenommen, weil
sie sich beim Testen als unnoetig und riskant herausstellte:
- .table-wrap: overflow-x:auto (aus v1.0.9) wieder auf overflow:hidden
  zurueckgesetzt. Grund: JEDES Template mit .table-wrap hat bereits einen
  eigenen <div style="overflow-x:auto;"> exakt um die <table> -- die
  zusaetzliche Scroll-Faehigkeit der Karte selbst war komplett redundant
  und erzeugte einen verschachtelten doppelten Scroll-Container um
  denselben Inhalt. Beim Testen der aufklappbaren Rechte-Tabelle
  (Gruppen-Seite) fuehrte das zu Inhalten, die bei negativem x dauerhaft
  unsichtbar wurden. table.data-table { min-width: 560px; } bleibt
  bestehen (harmlos, der jeweils vorhandene innere Wrapper scrollt
  weiterhin korrekt).

Eine dritte vermutete Ursache (aufgeklappte Rechte-Tabelle "verschwindet"
beim Anklicken des Chevrons) stellte sich NICHT als echter App-Bug heraus,
sondern als Artefakt von Playwrights eigenem automatischem
"scroll-into-view-vor-Klick"-Verhalten (der Chevron sitzt in der letzten
Tabellenspalte) -- per direktem JS-Aufruf von toggleDetail() ohne
Playwright-Klick liess sich das nicht reproduzieren, die Seite verhaelt
sich für echte Nutzer korrekt.

Alle 8 getesteten Seiten x 4 Viewports (32 Kombinationen) zeigen nach den
Fixes keinerlei horizontalen Overflow mehr (dokumentseitenweite
scrollWidth == clientWidth in jedem Fall).

VERSION 1.0.9 -> 1.0.10.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 14:29:48 +02:00
alientimandClaude Sonnet 5 a3f5f028f2 Responsives CSS ausgebaut: Handy (iPhone) und Tablet (iPad) sauber nutzbar
Bestehendes eigenes Design-System beibehalten (kein Bootstrap-Framework
eingebunden), stattdessen die vorhandenen Media Queries/Breakpoints
(Sidebar kollabiert bereits ab 900px) um die fehlenden Faelle ergaenzt:

- .table-wrap: overflow:hidden -> overflow-x:auto. Tabellen mit vielen
  Spalten (Geraete, Switche, Papierkorb, Benutzer, ...) haben auf
  schmalen Screens bisher entweder Spalten lautlos abgeschnitten oder
  die ganze Seite waagerecht aufgerissen -- jetzt scrollt NUR die
  Tabelle selbst (gleiches Prinzip wie Bootstraps .table-responsive).
  table.data-table bekommt zusaetzlich eine min-width als sinnvolle
  Scroll-Schwelle.
- .settings-grid: minmax(340px, 1fr) -> minmax(min(340px, 100%), 1fr) --
  ein nackter Pixel-Mindestwert erzwingt auf einem 375px-iPhone sonst
  waagerechten Overflow der ganzen Seite, sobald die Spalte breiter als
  der verfuegbare Platz ist. Neue Modifier-Klasse .settings-grid--wide
  (420px-Variante) ersetzt zwei betroffene inline
  style="grid-template-columns:minmax(420px,...)"-Overrides (LDAP-,
  NGINX-Seite), die denselben 100%-Fallback nicht hatten.
- .permission-group-col: eigenes overflow-x:auto zusaetzlich zum
  bestehenden flex-wrap der Elternzeile -- falls eine einzelne
  Rechte-Spalte (z.B. Geraete mit R/W/E/D) auch nach dem Umbruch auf
  eine eigene Zeile noch zu breit ist.
- Ab 900px (bestehender Sidebar-Breakpoint) zusaetzlich: .flex (generische
  Button-/Toolbar-Zeilen), .section-head, .term-toolbar/.log-toolbar und
  .detail-row brechen um statt seitlich ueberzulaufen; .modal-footer
  ebenso; .search-input in Tabellen-Toolbars schrumpft flexibel.
- Ab 640px: nebeneinander angeordnete Formularfelder (z.B. HTTP-/HTTPS-
  Port bei NGINX) stapeln sich auf volle Breite statt zwei sehr schmale
  Eingaben zu erzwingen; Modal-Innenabstand etwas schmaler.
- Ab 560px: Buttons in Kopfzeilen (section-head) nehmen die volle Breite
  ein statt als schmaler Block rechts zu kleben.

Live auf Testsystem-Update-Restart verifiziert: alle Kernseiten
(Dashboard, Clients, Switche, Gruppen, LDAP, NGINX, Live-Log, Wartung)
laden weiterhin fehlerfrei (200) nach dem CSS-/Template-Austausch.

WICHTIGER HINWEIS: In dieser Umgebung steht kein Browser-Screenshot-
Werkzeug zur Verfuegung, die Aenderungen wurden also nicht pixelgenau auf
echten iPhone-16-Pro-/iPad-Bildschirmgroessen gegengeprueft, sondern rein
ueber CSS-Analyse und Seiten-Ladetests. Ein Blick auf einem echten Geraet
(oder per Browser-Devtools-Emulation) wird empfohlen.

VERSION 1.0.8 -> 1.0.9.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 12:34:35 +02:00
alientimandClaude Sonnet 5 5bca29f3da NGINX-Seite: Sichtbarkeit/Kontrolle der automatischen Let's-Encrypt-Verlaengerung
certbot richtet die automatische Verlaengerung (certbot.timer, zweimal
taeglich) zwar schon beim Paket-Install selbst ein -- das war fuer den
Admin in der Weboberflaeche bisher aber unsichtbar, reine Behauptung im
Code-Kommentar. Jetzt sichtbar/nachpruefbar:

- Neue Zeile "Automatische Verlaengerung" im Zertifikat-Status (nur bei
  Let's-Encrypt-Zertifikaten): Timer aktiv/inaktiv + naechster geplanter
  Lauf (_certbot_timer_status(), liest systemctl is-active/list-timers).
- "Verlaengerung testen (Dry-Run)" -- certbot renew --dry-run, aendert
  nichts, zeigt nur ob die Verlaengerung im Ernstfall funktionieren wuerde.
- "Jetzt manuell verlaengern" -- certbot renew --force-renewal, fuer
  Testzwecke oder eine sofortige Schluessel-Rotation (mit Warnhinweis auf
  Let's Encrypts Rate-Limits).
- Hinweistext beim Anfordern zeigt den Timer-Status schon VOR der ersten
  Anforderung.

VERSION 1.0.7 -> 1.0.8 (ab hier wieder normale fortlaufende
Versionierung statt erneutem Release derselben Versionsnummer).

Live auf Testsystem-Update-Restart verifiziert: Seite laedt weiterhin
fehlerfrei (_certbot_timer_status crasht nicht ohne aktives
Let's-Encrypt-Zertifikat), certbot.timer selbst bestaetigt aktiv
(naechster Lauf laut systemctl list-timers korrekt angezeigt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 12:24:15 +02:00
5 changed files with 153 additions and 8 deletions
+1 -1
View File
@@ -1 +1 @@
1.0.7 1.0.10
+44
View File
@@ -2832,6 +2832,25 @@ def _request_letsencrypt_cert(domain, email=None):
return _dhcp_run_privileged(args, timeout=120) return _dhcp_run_privileged(args, timeout=120)
def _certbot_timer_status():
"""Status von certbot.timer -- dem systemd-Timer, der die automatische
Verlängerung tatsächlich antreibt (Standard-Paketverhalten unter
Debian/Ubuntu, zweimal täglich). Rein informativ für die NGINX-Seite:
"automatische Verlängerung" soll für den Admin sichtbar/nachprüfbar
sein, nicht nur eine Behauptung im Quelltext bleiben. None, wenn
certbot (noch) nicht installiert ist."""
active_ok, active_out = _dhcp_run_privileged(["systemctl", "is-active", "certbot.timer"], timeout=5)
if not active_ok and "could not be found" in active_out.lower():
return None
next_run = None
ok, out = _dhcp_run_privileged(["systemctl", "list-timers", "certbot.timer", "--no-legend"], timeout=5)
if ok and out.strip():
parts = out.strip().split(None, 4)
if len(parts) >= 4:
next_run = " ".join(parts[0:4])
return {"active": active_out.strip() == "active", "next_run": next_run}
@app.route("/settings/nginx/raw") @app.route("/settings/nginx/raw")
@login_required @login_required
def settings_nginx_raw(): def settings_nginx_raw():
@@ -2910,6 +2929,30 @@ def settings_nginx():
"success", "success",
) )
elif "test_renewal" in request.form:
if get_setting("ssl_source", "upload") != "letsencrypt":
flash("Verlängerungs-Test setzt ein per Let's Encrypt ausgestelltes Zertifikat voraus.", "danger")
else:
domain = get_setting("nginx_server_name", "_")
ok, out = _dhcp_run_privileged(["certbot", "renew", "--dry-run", "--cert-name", domain], timeout=120)
if ok:
log_action("settings.nginx_letsencrypt_test_renewal", domain)
flash("Verlängerungs-Test erfolgreich (Dry-Run — es wurde nichts tatsächlich verändert).", "success")
else:
flash(f"Verlängerungs-Test fehlgeschlagen: {out.strip()[-800:]}", "danger")
elif "force_renew" in request.form:
if get_setting("ssl_source", "upload") != "letsencrypt":
flash("Manuelle Verlängerung setzt ein per Let's Encrypt ausgestelltes Zertifikat voraus.", "danger")
else:
domain = get_setting("nginx_server_name", "_")
ok, out = _dhcp_run_privileged(["certbot", "renew", "--force-renewal", "--cert-name", domain], timeout=120)
if ok:
log_action("settings.nginx_letsencrypt_force_renew", domain)
flash("Zertifikat manuell erneuert — nginx wurde über den Deploy-Hook automatisch neu geladen.", "success")
else:
flash(f"Manuelle Verlängerung fehlgeschlagen: {out.strip()[-800:]}", "danger")
elif "apply_nginx" in request.form: elif "apply_nginx" in request.form:
new_server_name = request.form.get("server_name", "_").strip() or "_" new_server_name = request.form.get("server_name", "_").strip() or "_"
new_http_port = request.form.get("http_port", "80").strip() new_http_port = request.form.get("http_port", "80").strip()
@@ -3007,6 +3050,7 @@ def settings_nginx():
https_port=get_setting("nginx_https_port", "443"), https_port=get_setting("nginx_https_port", "443"),
pending_nginx_token=pending_token, pending_nginx_token=pending_token,
nginx_revert_seconds=NGINX_REVERT_SECONDS, nginx_revert_seconds=NGINX_REVERT_SECONDS,
certbot_timer=_certbot_timer_status(),
) )
+78 -2
View File
@@ -197,13 +197,23 @@ button { font-family: inherit; }
.nav-group.expanded .nav-group-children { display: flex; } .nav-group.expanded .nav-group-children { display: flex; }
.nav-group-children .nav-item { font-size: 13px; padding: 8px 12px; } .nav-group-children .nav-item { font-size: 13px; padding: 8px 12px; }
/* Kacheln nebeneinander (z.B. Im-/Export, Konto-Seite) */ /* Kacheln nebeneinander (z.B. Im-/Export, Konto-Seite). minmax(min(...,
100%), 1fr) statt eines nackten Pixelwerts -- eine Spalten-Mindestbreite
von z.B. 340px würde auf einem 375px-iPhone (abzüglich .content-Padding)
sonst waagerechten Overflow der ganzen Seite erzwingen, weil "auto-fit"
erst ab einer Breite größer als der Mindestwert umbricht. */
.settings-grid { .settings-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
gap: 24px; gap: 24px;
align-items: start; align-items: start;
} }
/* Modifier für Seiten, die etwas breitere Karten brauchen (z.B. NGINX,
LDAP) -- ersetzt frühere inline style="grid-template-columns:..."
Overrides, die denselben 100%-Fallback nicht hatten. */
.settings-grid--wide {
grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
}
/* Navbar-Reihenfolge (Settings) */ /* Navbar-Reihenfolge (Settings) */
.nav-order-list { .nav-order-list {
@@ -683,8 +693,19 @@ button { font-family: inherit; }
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--border-soft); border: 1px solid var(--border-soft);
border-radius: var(--radius); border-radius: var(--radius);
/* Zurück auf overflow:hidden (nur für die abgerundeten Ecken) --
JEDES Template, das .table-wrap verwendet, hat bereits einen eigenen
<div style="overflow-x:auto;"> exakt um die <table> herum (nicht um
die ganze Karte). overflow-x:auto zusätzlich HIER hätte einen zweiten,
verschachtelten Scroll-Container um denselben Inhalt erzeugt -- live
auf einem iPhone-Viewport nachgewiesen: bei der aufklappbaren
Rechte-Tabelle (Gruppen-Seite) landete der Inhalt dadurch dauerhaft
unsichtbar bei negativem x. Die Tabellen scrollen also weiterhin
horizontal, nur über den bereits vorhandenen inneren Wrapper, nicht
zusätzlich über die Karte selbst. */
overflow: hidden; overflow: hidden;
} }
.table-wrap table.data-table { min-width: 560px; }
.table-toolbar { .table-toolbar {
display: flex; display: flex;
@@ -792,6 +813,12 @@ table.data-table {
========================================================================== */ ========================================================================== */
.field { margin-bottom: 15px; } .field { margin-bottom: 15px; }
/* Zwei .field nebeneinander in einer .flex-Zeile (z.B. HTTP-/HTTPS-Port
bei NGINX) -- als Klasse statt inline style="flex:1", damit die
Mobile-Stapel-Regel (siehe @media max-width:640px weiter unten) sie
per externem Stylesheet überschreiben kann; ein inline style gewinnt
sonst grundsätzlich gegen jede @media-Regel, ganz gleich wie spezifisch. */
.field--half { flex: 1; min-width: 0; }
.field label { .field label {
display: block; display: block;
font-size: 12.5px; font-size: 12.5px;
@@ -1156,8 +1183,14 @@ select {
Logs mit nur R). Feste/zu schmale Breiten haben die D-Spalte bei Logs mit nur R). Feste/zu schmale Breiten haben die D-Spalte bei
Geräte zuvor lautlos in den Overflow geschoben. */ Geräte zuvor lautlos in den Overflow geschoben. */
flex: 0 0 auto; flex: 0 0 auto;
max-width: 100%;
padding-right: 28px; padding-right: 28px;
border-right: 1px solid var(--border-soft); border-right: 1px solid var(--border-soft);
/* Fällt eine einzelne Spalte (z.B. Geräte mit vielen Rechten-Spalten)
trotz .permission-groups-row's flex-wrap auf einem schmalen Screen
für sich genommen noch zu breit aus, scrollt nur diese eine Spalte
waagerecht statt die Seite aufzureißen. */
overflow-x: auto;
} }
.permission-group-col:last-child { border-right: none; padding-right: 0; } .permission-group-col:last-child { border-right: none; padding-right: 0; }
.permission-group-header-cell { padding-left: 0 !important; } .permission-group-header-cell { padding-left: 0 !important; }
@@ -1286,14 +1319,57 @@ select {
.content { padding: 18px 16px 40px; } .content { padding: 18px 16px 40px; }
.topbar { padding: 10px 16px; } .topbar { padding: 10px 16px; }
.topbar-logo { opacity: 0.5; } .topbar-logo { opacity: 0.5; }
/* Ab hier iPad-Breite (900px ist bereits der bestehende Sidebar-
Umschaltpunkt) und schmaler: generische Button-/Toolbar-Zeilen
(.flex) sollen umbrechen statt seitlich überzulaufen -- betrifft vor
allem Kopfzeilen mit mehreren Aktions-Buttons (z.B. NGINX-, Logs-,
Verlauf-Seite) und nebeneinander angeordnete Formularfelder. */
.flex { flex-wrap: wrap; }
.section-head { flex-wrap: wrap; }
.term-toolbar, .log-toolbar { flex-wrap: wrap; row-gap: 8px; }
.detail-row { flex-wrap: wrap; }
.detail-row .v { text-align: left; }
/* Modal-Fußzeilen (Speichern/Abbrechen) und Tabellen-Kopfzeilen
(Suche + Aktion) sollen bei wenig Platz ebenfalls umbrechen statt
Buttons/Suchfeld ineinanderzuschieben. */
.modal-footer { flex-wrap: wrap; }
.table-toolbar .search-input { min-width: 0; flex: 1 1 160px; }
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.topbar-logo { display: none; } .topbar-logo { display: none; }
/* Formulare mit zwei nebeneinander angeordneten Feldern (class="flex
gap-2" um zwei .field-Divs, z.B. HTTP-/HTTPS-Port bei NGINX) stapeln
auf Handy-Breite volle Breite pro Feld statt zwei sehr schmale
Eingaben nebeneinander zu erzwingen. */
form .flex > .field { flex-basis: 100%; }
.modal-header, .modal-body, .modal-footer { padding-left: 16px; padding-right: 16px; }
} }
@media (max-width: 560px) { @media (max-width: 560px) {
.device-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); } .device-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.topbar-title { font-size: 16px; } .topbar-title { font-size: 16px; }
.stat-row { grid-template-columns: repeat(2, 1fr); } .stat-row { grid-template-columns: repeat(2, 1fr); }
/* Button-Leisten in Kopfzeilen (z.B. section-head/card-Kopf mit
"RAW anzeigen" o.ä.) nehmen auf sehr schmalen Screens die volle
Breite ein statt als schmaler Block rechts zu kleben. */
.section-head > .btn,
.section-head > .btn-sm { flex: 1 1 100%; }
/* Auf einem iPhone quetschen die beiden rechten Topbar-Pillen (Prüf-
Countdown + DHCP) .topbar-left (flex:1 1 0%, min-width:0) so weit
zusammen, dass der Seitentitel über seine eigene Spaltenbreite
hinausläuft und sichtbar mit der Pille kollidiert (per Playwright-
Screenshot auf 393px Breite nachgewiesen: .topbar-title ragte 24.5px
über .topbar-lefts berechnete Breite hinaus). Titel und Pillen
bekommen ab hier je eine eigene Zeile statt sich einen Platz zu
teilen, der für beide zusammen nicht reicht. */
.topbar { flex-wrap: wrap; row-gap: 6px; }
.topbar-left { flex: 1 1 100%; }
.topbar-right { flex: 1 1 100%; justify-content: flex-start; }
} }
+1 -1
View File
@@ -5,7 +5,7 @@
{% block page_sub %}<div class="topbar-sub">Anmeldung mit dem Domänen-Passwort, zusätzlich zu lokalen Konten</div>{% endblock %} {% block page_sub %}<div class="topbar-sub">Anmeldung mit dem Domänen-Passwort, zusätzlich zu lokalen Konten</div>{% endblock %}
{% block content %} {% block content %}
<div class="settings-grid" style="grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));"> <div class="settings-grid settings-grid--wide">
<div class="card card-pad"> <div class="card card-pad">
<div class="section-head" style="margin-bottom:16px;"> <div class="section-head" style="margin-bottom:16px;">
+29 -4
View File
@@ -5,7 +5,7 @@
{% block page_sub %}<div class="topbar-sub">Reverse-Proxy: Domain, Ports, SSL/HSTS und Zertifikat</div>{% endblock %} {% block page_sub %}<div class="topbar-sub">Reverse-Proxy: Domain, Ports, SSL/HSTS und Zertifikat</div>{% endblock %}
{% block content %} {% block content %}
<div class="settings-grid" style="grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));"> <div class="settings-grid settings-grid--wide">
<div class="card card-pad"> <div class="card card-pad">
<div class="section-head" style="margin-bottom:16px;"> <div class="section-head" style="margin-bottom:16px;">
@@ -43,11 +43,11 @@
<div class="field-hint">"_" ist nginx' Catch-all (Standard für interne Instanzen ohne eigene Domain) — für Let's Encrypt muss hier die tatsächliche, öffentlich auflösbare Domain stehen.</div> <div class="field-hint">"_" ist nginx' Catch-all (Standard für interne Instanzen ohne eigene Domain) — für Let's Encrypt muss hier die tatsächliche, öffentlich auflösbare Domain stehen.</div>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<div class="field" style="flex:1;"> <div class="field field--half">
<label for="http_port">HTTP-Port</label> <label for="http_port">HTTP-Port</label>
<input type="number" name="http_port" id="http_port" min="1" max="65535" value="{{ http_port }}" required> <input type="number" name="http_port" id="http_port" min="1" max="65535" value="{{ http_port }}" required>
</div> </div>
<div class="field" style="flex:1;"> <div class="field field--half">
<label for="https_port">HTTPS-Port</label> <label for="https_port">HTTPS-Port</label>
<input type="number" name="https_port" id="https_port" min="1" max="65535" value="{{ https_port }}" required> <input type="number" name="https_port" id="https_port" min="1" max="65535" value="{{ https_port }}" required>
</div> </div>
@@ -108,7 +108,32 @@
{% endif %} {% endif %}
</span> </span>
</div> </div>
{% if cert_info.source == "letsencrypt" %}
<div class="detail-row">
<span class="k">Automatische Verlängerung</span>
<span class="v">
{% if certbot_timer and certbot_timer.active %}
<span class="pill online">aktiv</span>
{% if certbot_timer.next_run %}<span class="text-faint" style="font-size:11px; display:block; margin-top:2px;">nächster Lauf: {{ certbot_timer.next_run }}</span>{% endif %}
{% elif certbot_timer %}
<span class="pill offline">inaktiv</span>
{% else %}
<span class="pill unknown">unbekannt</span>
{% endif %}
</span>
</div>
{% endif %}
</div> </div>
{% if cert_info.source == "letsencrypt" and can_edit %}
<div class="flex gap-2" style="margin-bottom:18px;">
<form method="post" style="flex:1;">
<button type="submit" name="test_renewal" value="1" class="btn btn-secondary btn-block btn-sm">Verlängerung testen (Dry-Run)</button>
</form>
<form method="post" style="flex:1;" data-confirm="Zertifikat jetzt sofort erneuern (zählt gegen Let's Encrypts Rate-Limits)?">
<button type="submit" name="force_renew" value="1" class="btn btn-secondary btn-block btn-sm">Jetzt manuell verlängern</button>
</form>
</div>
{% endif %}
{% else %} {% else %}
<p class="text-faint" style="font-size:12.5px; margin-bottom:18px;">Noch kein Zertifikat hinterlegt.</p> <p class="text-faint" style="font-size:12.5px; margin-bottom:18px;">Noch kein Zertifikat hinterlegt.</p>
{% endif %} {% endif %}
@@ -116,7 +141,7 @@
{% if can_edit %} {% if can_edit %}
<div class="field"> <div class="field">
<label style="font-weight:600; font-size:13px;">Let's Encrypt anfordern</label> <label style="font-weight:600; font-size:13px;">Let's Encrypt anfordern</label>
<div class="field-hint" style="margin-bottom:8px;">Setzt voraus, dass die Domain öffentlich auf diesen Host auflöst und Port 80 aus dem Internet erreichbar ist (HTTP-01-Validierung) — für rein interne Instanzen ohne öffentliche Domain nicht nutzbar, dann stattdessen hochladen.</div> <div class="field-hint" style="margin-bottom:8px;">Setzt voraus, dass die Domain öffentlich auf diesen Host auflöst und Port 80 aus dem Internet erreichbar ist (HTTP-01-Validierung) — für rein interne Instanzen ohne öffentliche Domain nicht nutzbar, dann stattdessen hochladen. Verlängerung läuft danach automatisch über certbots eigenen Timer{% if certbot_timer %} ({{ "aktiv" if certbot_timer.active else "inaktiv!" }}){% endif %}, zweimal täglich.</div>
</div> </div>
<form method="post" style="margin-bottom:18px;"> <form method="post" style="margin-bottom:18px;">
<div class="field"> <div class="field">