Files
KantSites/templates/admin/users.html.twig
T
kantarciandClaude Fable 5.2 ead47cc924 Keep old pages readable, detect stale settings writes, make backups honest
- Own CSS is parsed on write only. A page whose stored CSS no longer
  passes a stricter rule answers 200 and opens in the editor; that one
  block loses its CSS. A document read several times in one request is
  validated once.
- Settings and theme refuse a write from an outdated form.
- A backup fails when a media file does not arrive completely and
  leaves no half directory behind; rotation counts intact backups only.
- The archive update takes its backup behind the maintenance page.
- A package import puts all media in place before removing the old
  ones, reports honestly when that fails and keeps a maintenance state
  that was already on.
- Restoring replaces the SQLite file in one move and points at pending
  migrations.
- Six irreversible actions ask first, in a dialog of the admin's own.
- Ctrl+S in the editor takes the field being typed in along; Delete
  no longer removes a block while a dialog is open.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 11:41:05 +01:00

111 lines
7.2 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'admin/shell.html.twig' %}
{% set nav = 'users' %}
{% block title %}Benutzer{% endblock %}
{% block heading %}Benutzer{% endblock %}
{% block lead %}Jedes Konto hat genau eine Rolle. Ein Passwort oder eine Rolle zu ändern meldet alle offenen Sitzungen dieses Kontos ab – das ist der Weg, jemanden sofort auszusperren.{% endblock %}
{% block content %}
<details class="card"{% if app.flashes is not empty %} open{% endif %}>
<summary>Neues Konto anlegen</summary>
<form action="{{ path('admin_user_create') }}" method="post" class="card-body">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('users') }}">
<div class="fields">
<div>
<label for="new-username">Benutzername</label>
<input id="new-username" name="username" maxlength="100" required autocomplete="off" pattern="[a-z0-9][a-z0-9._-]{2,99}">
<p class="field-help">3 bis 100 Zeichen: Kleinbuchstaben, Ziffern, Punkt, Unterstrich, Bindestrich.</p>
</div>
<div>
<label for="new-password">Passwort</label>
<input id="new-password" name="password" type="password" required autocomplete="new-password" minlength="{{ minPassword }}" maxlength="{{ maxPassword }}">
<p class="field-help">Mindestens {{ minPassword }} Zeichen, höchstens {{ maxPassword }} Bytes. Gib es der Person auf einem anderen Weg als per E-Mail weiter; sie kann es unter „Mein Konto“ selbst ändern.</p>
</div>
<div>
<label for="new-role">Rolle</label>
<select id="new-role" name="role">
{% for value, label in roles %}<option value="{{ value }}"{{ value == 'ROLE_EDITOR' ? ' selected' : '' }}>{{ label }}</option>{% endfor %}
</select>
<p class="field-help">{% for value, help in roleHelp %}<strong>{{ roles[value] }}:</strong> {{ help }}{% if not loop.last %}<br>{% endif %}{% endfor %}</p>
</div>
</div>
<button class="primary" type="submit">Konto anlegen</button>
</form>
</details>
<div class="section-head"><h2>Konten <span class="count">{{ users|length }}</span></h2><span class="muted">Zeiten in UTC.</span></div>
<div class="rows">
{% for account in users %}
<article class="row row-stack">
<div class="row-line">
<span class="row-icon" aria-hidden="true">☺</span>
<div class="row-main">
<h3>{{ account.username }}{% if account.id == me %} <span class="pill">dein Konto</span>{% endif %}</h3>
<p>{{ account.roleName }}
· {% if account.lastLoginAt %}zuletzt angemeldet {{ account.lastLoginAt|date('d.m.Y H:i') }}{% else %}noch nie angemeldet{% endif %}
{% if account.passwordChangedAt %} · Passwort vom {{ account.passwordChangedAt|date('d.m.Y') }}{% endif %}
{% if account.createdAt %} · angelegt {{ account.createdAt|date('d.m.Y') }}{% endif %}
</p>
</div>
<span class="pill {{ 'ROLE_ADMIN' == account.role ? 'pill-live' : ('ROLE_PUBLISHER' == account.role ? 'pill-draft' : 'pill-off') }}">{{ account.roleName }}</span>
</div>
<details class="sheet">
<summary aria-label="Einstellungen von {{ account.username }}">Einstellungen</summary>
<div class="sheet-body">
<div class="sheet-part">
<h4>Rolle</h4>
{% if account.id == me %}
<p>Die eigene Rolle lässt sich hier nicht ändern. Das soll ein zweiter Administrator tun – sonst kann sich der letzte Administrator selbst die Rechte nehmen.</p>
{% else %}
<form action="{{ path('admin_user_role', {id: account.id}) }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('users') }}">
<div class="field">
<label for="role-{{ account.id }}">Rolle von {{ account.username }}</label>
<select id="role-{{ account.id }}" name="role">
{% for value, label in roles %}<option value="{{ value }}"{{ value == account.role ? ' selected' : '' }}>{{ label }}</option>{% endfor %}
</select>
<p class="field-help">Offene Sitzungen dieses Kontos werden dabei abgemeldet.</p>
</div>
<button class="secondary" type="submit">Rolle ändern</button>
</form>
{% endif %}
</div>
<div class="sheet-part">
<h4>Passwort setzen</h4>
<p>Setzt ein neues Passwort, ohne das alte zu kennen – für den Fall, dass jemand nicht mehr hineinkommt. Alle offenen Sitzungen dieses Kontos werden abgemeldet.</p>
<form action="{{ path('admin_user_password', {id: account.id}) }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('users') }}">
<div class="field">
<label for="password-{{ account.id }}">Neues Passwort für {{ account.username }}</label>
<input id="password-{{ account.id }}" name="password" type="password" required autocomplete="new-password" minlength="{{ minPassword }}" maxlength="{{ maxPassword }}">
</div>
<button class="secondary" type="submit">Passwort setzen</button>
</form>
</div>
{% if account.id != me %}
<div class="sheet-part">
<h4>Konto löschen</h4>
<p>Entfernt „{{ account.username }}“. Gespeicherte Inhalte bleiben unberührt – Seiten und Fassungen hängen nicht am Konto. Das eigene Konto lässt sich nicht löschen; deshalb bleibt immer mindestens ein Administrator übrig.</p>
<form action="{{ path('admin_user_delete', {id: account.id}) }}" method="post" data-confirm-title="Konto „{{ account.username }}“ löschen?" data-confirm-text="Die Person kann sich danach nicht mehr anmelden. Seiten und Fassungen bleiben." data-confirm-label="Konto löschen" data-confirm-variant="danger">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('users') }}">
<button class="subtle danger-text" type="submit">Konto löschen</button>
</form>
</div>
{% endif %}
</div>
</details>
</article>
{% endfor %}
</div>
<div class="card">
<div class="card-head"><h2>Wenn kein Konto mehr funktioniert</h2><span class="muted">Zugang über die Konsole</span></div>
<div class="card-body">
<p class="muted">Auf dem Server legt <code>php bin/console cms:user:create name --role=admin</code> ein Konto an; das Passwort wird verdeckt abgefragt und ein vorhandenes Konto nie überschrieben. Das ist der Weg zurück, wenn niemand mehr hineinkommt – er braucht Dateizugriff auf den Server und lässt sich hier nicht ersetzen.</p>
</div>
</div>
<p class="footnote">KantSites · Arbeitsstand</p>
{% endblock %}