Keine Beschreibung
  • Go 85.2%
  • HTML 6.6%
  • CSS 4.8%
  • JavaScript 2.2%
  • Makefile 0.8%
  • Andere 0.4%
Datei suchen
Robert Evers 220c755fa1
Alle Prüfungen waren erfolgreich
build / go-build (push) Successful in 38s
release / release (push) Successful in 1m31s
Merge pull request 'added inital mailsupport' (#4) from develop_mail into main
Reviewed-on: #4
2026-07-08 12:05:26 +02:00
.devcontainer added fork notice 2026-07-06 21:49:44 +02:00
.forgejo/workflows verbosity pipeline tests 2026-07-08 12:01:55 +02:00
files/data Initial commit 2026-02-26 18:09:29 +01:00
screenshots Initial commit 2026-02-26 18:09:29 +01:00
static claude added inital mailsupport 2026-07-08 12:04:59 +02:00
templates claude added inital mailsupport 2026-07-08 12:04:59 +02:00
.dockerignore added fork notice 2026-07-06 21:49:44 +02:00
.editorconfig feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
.env.example claude added inital mailsupport 2026-07-08 12:04:59 +02:00
.gitignore added fork notice 2026-07-06 21:49:44 +02:00
api.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
api_test.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
auth.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
auth_test.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
docker-compose.yml feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
Dockerfile feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
Dockerfile.alpine feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
env.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
env_test.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
go.mod feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
handlers_admin.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
handlers_manage.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
handlers_poll.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
handlers_static.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
handlers_test.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
LICENSE Update LICENSE 2026-02-26 18:35:04 +01:00
mail.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
mail_test.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
main.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
Makefile added fork notice 2026-07-06 21:49:44 +02:00
password.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
password_test.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
poll.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
poll_test.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
proxy.go added fork notice 2026-07-06 21:49:44 +02:00
proxy_test.go added fork notice 2026-07-06 21:49:44 +02:00
ratelimit.go added fork notice 2026-07-06 21:49:44 +02:00
ratelimit_test.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
README.md claude added inital mailsupport 2026-07-08 12:04:59 +02:00
server.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00
statistik.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
statistik_test.go feature update, migration to go with ai 2026-07-06 19:29:28 +02:00
util.go claude added inital mailsupport 2026-07-08 12:04:59 +02:00

Der KonsensOmat

Fork-Hinweis: Dies ist ein Fork von OpenKunde/konsensomat (PHP). Diese Version wurde vollständig nach Go portiert und seitdem eigenständig weiterentwickelt.

KonsensOmat ist ein schlankes Open-Source-Tool zur Entscheidungsfindung nach dem Prinzip des Systemic Consensing (Systemisches Konsensieren, SK): https://en.wikipedia.org/wiki/Systemic_Consensing

Statt Mehrheiten zu zählen, wird der Widerstand gegenüber Vorschlägen bewertet. Die Option mit dem geringsten Gesamtwiderstand gilt als tragfähigste Lösung.

Hinweis: Das Tool ist derzeit nur auf Deutsch verfügbar.


Eigenschaften

  • Bewertung nach dem Widerstandsprinzip (SK)
  • Anonyme Teilnahme (Pseudonyme möglich)
  • Keine Registrierung, keine Datenbank
  • Speicherung als einfache Dateien
  • Laufzeit pro Umfrage von der Erstellerin/vom Ersteller wählbar, automatische Löschung danach (Obergrenze konfigurierbar, Standard: 7 Tage)
  • Optionaler Passwortschutz pro Umfrage
  • Nur Erstellerin/Ersteller (und optional Admins) können eine Umfrage löschen – nicht jede Person mit dem Link
  • Keine externen Dienste oder Tracker
  • Light- und Dark-Mode (folgt der Systemeinstellung, manuell umschaltbar)
  • Öffentliche Statistik-Seite (/statistik) mit Aktivitäts-Heatmap der letzten 180 Tage
  • Rate-Limiting gegen wiederholtes Passwort-Raten (Umfrage- und Admin-Login)
  • Sicherheits-Header (CSP, X-Frame-Options, HSTS, …) und CSRF-Schutz für alle Formulare
  • Infoseiten zu systemischem Konsensieren, Impressum und Datenschutz
  • Optionaler Mail-Versand: Personen per Mail einladen, eine Wiederherstellungs-Mail mit Umfrage- und Verwaltungslink beim Erstellen, sowie (falls von der Umfrage-Erstellerin/dem -Ersteller erlaubt) ein individueller Mail-Link für Teilnehmende, um ihre eigene Stimme später anzupassen (siehe Mail-Funktionen)

Voraussetzungen

  • Go 1.26 oder höher
  • Schreibrechte für files/data

Lokale Nutzung

Repository klonen:

git clone https://github.com/rkl110/konsensomat.git
cd konsensomat

Go-Version prüfen:

go version

Lokalen Server starten:

go run .

Im Browser öffnen:

http://localhost:8080

Optional lässt sich die Konfiguration über Umgebungsvariablen anpassen:

KONSENSOMAT_ADDR=:8080 KONSENSOMAT_DATA_DIR=files/data KONSENSOMAT_EXPIRY_DAYS=14 go run .
Variable Standard Bedeutung
KONSENSOMAT_ADDR :8080 Adresse, auf der der Server lauscht
KONSENSOMAT_DATA_DIR files/data Verzeichnis für die Umfrage-JSON-Dateien
KONSENSOMAT_EXPIRY_DAYS 7 Maximale und vorausgewählte Laufzeit einer Umfrage in Tagen (max. 365)
KONSENSOMAT_ADMIN_PASSWORD (leer = aus) Admin-Passwort für /admin (siehe Zugriffsschutz & Löschen)
KONSENSOMAT_TRUSTED_PROXIES (leer) Vertrauenswürdige Reverse-Proxy-IPs/-CIDRs (siehe Hinter einem Reverse-Proxy betreiben)
KONSENSOMAT_BASE_URL (leer) Öffentliche Basis-URL für Links in ausgehenden Mails, überschreibt die aus der Anfrage abgeleitete (siehe Mail-Funktionen)
KONSENSOMAT_MAIL_ENABLED false Hauptschalter für den Mail-Versand (siehe Mail-Funktionen)
KONSENSOMAT_SMTP_HOST (leer) SMTP-Host für den Mail-Versand
KONSENSOMAT_SMTP_PORT 587 Port des SMTP-Servers
KONSENSOMAT_SMTP_FROM (leer) Absenderadresse (erforderlich, sobald KONSENSOMAT_SMTP_HOST gesetzt ist)
KONSENSOMAT_SMTP_FROM_NAME KonsensOmat Anzeigename im From:-Header
KONSENSOMAT_SMTP_USERNAME (leer) SMTP-Benutzername, falls der Server Authentifizierung verlangt
KONSENSOMAT_SMTP_PASSWORD (leer) SMTP-Passwort
KONSENSOMAT_SMTP_TLS starttls starttls (opportunistisch, funktioniert auch mit einem TLS-losen lokalen Mailsystem) oder implicit (TLS von Anfang an, z.B. Port 465)

Konfiguration über .env

Statt Umgebungsvariablen von Hand zu setzen, kann eine .env-Datei im Arbeitsverzeichnis abgelegt werden (siehe .env.example):

cp .env.example .env
KONSENSOMAT_ADDR=:8080
KONSENSOMAT_DATA_DIR=files/data
KONSENSOMAT_EXPIRY_DAYS=7
#KONSENSOMAT_ADMIN_PASSWORD=
#KONSENSOMAT_TRUSTED_PROXIES

Bereits gesetzte echte Umgebungsvariablen haben immer Vorrang vor der .env-Datei. Die .env selbst wird nicht versioniert (siehe .gitignore).

Tests ausführen

go test ./...

Mit Race-Detector (empfohlen, da Rate-Limiter und Statistik-Cache nebenläufig zugreifen):

go test -race ./...

Zugriffsschutz & Löschen

Löschrecht: Beim Erstellen einer Umfrage wird im Browser der Erstellerin/des Erstellers automatisch ein Berechtigungs-Cookie gesetzt. Damit – und nur damit – kann die eigene Umfrage jederzeit wieder gelöscht werden. Wer nur den Link kennt, kann eine Umfrage nicht löschen, auch wenn kein Passwort gesetzt ist.

Zusätzlich kann beim Erstellen ein optionales Passwort vergeben werden. Ist eines gesetzt, müssen alle anderen Besucher*innen es einmalig eingeben, um die Umfrage überhaupt zu sehen oder abzustimmen – das Passwort gewährt aber kein Löschrecht, auch nicht nach dem Entsperren.

Eine Umfrage kann also, ob mit oder ohne Passwort, immer nur von der Erstellerin/dem Ersteller selbst (in ihrem/seinem Browser) oder von einem Admin gelöscht werden.

Admin-Link teilen: Auf der Umfrageseite sieht die Erstellerin/der Ersteller einen zusätzlichen Admin-Link (mit eingebettetem Berechtigungs-Token). Wird dieser Link in einem anderen Browser/Gerät geöffnet, gilt auch dieser fortan als Erstellerin der Umfrage – nützlich, um die Verwaltung mit Mitorganisatorinnen zu teilen, ohne mit ihnen den Umfrage-Link samt Passwort teilen zu müssen. Wer diesen Link kennt, kann die Umfrage löschen und ihr Passwort ändern.

Passwort, Frage/Vorschläge und Laufzeit nachträglich ändern: Wer verwalten darf (Erstellerin/Ersteller oder Admin), sieht auf der Umfrageseite einen (standardmäßig eingeklappten, per Klick aufklappbaren) Verwaltungsbereich, über den sich jederzeit das Passwort setzen/ändern/entfernen, die Frage samt Vorschlägen korrigieren und die verbleibende Laufzeit verlängern oder verkürzen lässt (weiterhin begrenzt auf KONSENSOMAT_EXPIRY_DAYS).

Statistik-Seite: /statistik ist ohne Anmeldung für alle einsehbar und zeigt ausschließlich aggregierte Nutzungszahlen (aktive Umfragen, davon gültig/ungültig, bald ablaufend, durchschnittliche/maximale Teilnehmerzahl) sowie eine Aktivitäts-Heatmap: eine Zelle pro Tag der letzten 180 Tage, eingefärbt nach der Anzahl an diesem Tag erstellter gültiger Umfragen (≥ 2 Teilnehmer*innen). Es werden dabei nie Inhalte oder Links einzelner Umfragen preisgegeben.

Admins: Ist KONSENSOMAT_ADMIN_PASSWORD gesetzt, können sich Admins unter /admin anmelden und danach jede Umfrage einsehen, verwalten und löschen – unabhängig von deren eigenem Passwort. Auf der Statistik-Seite (/statistik) sehen angemeldete Admins zusätzlich eine Liste aller aktiven Umfragen (Frage, Teilnehmerzahl, Ablaufdatum) mit direktem Link zur Verwaltung – ohne Admin-Login bleiben Umfragen weiterhin nur über ihren eigenen Link erreichbar. Das ist als Moderations-Werkzeug gedacht (z.B. um eine gemeldete, missbräuchliche Umfrage zu entfernen), nicht als reguläres Nutzerkonto. Ohne gesetztes Admin-Passwort ist die Funktion vollständig deaktiviert.

Laufzeit: Beim Erstellen wählt die Erstellerin/der Ersteller, wie viele Tage die Umfrage laufen soll (maximal KONSENSOMAT_EXPIRY_DAYS, das ist auch die Vorauswahl). Nach Ablauf wird die Umfrage automatisch gelöscht – unabhängig von Löschrecht oder Passwort.

Es gibt bewusst kein Nutzerkonto und keine Passwort-Wiederherstellung – gehen sowohl das Berechtigungs-Cookie als auch ein gesetztes Passwort verloren und ist kein Admin-Passwort konfiguriert, lässt sich die Umfrage nur noch über ihre automatische Löschung nach Ablauf der gewählten Laufzeit entfernen.

Brute-Force-Schutz: Falsche Passwort-Versuche (Umfrage- oder Admin-Passwort, über Web-UI oder API) werden pro Client-IP gezählt; nach 10 Fehlversuchen innerhalb von 5 Minuten wird diese IP für den jeweiligen Endpunkt vorübergehend gesperrt (HTTP 429). Erfolgreiche Versuche und Anfragen ganz ohne Passwort zählen nicht mit.


API

Neben der Weboberfläche lässt sich die gesamte Anwendung auch über eine JSON-API steuern. Die API folgt demselben Zugriffsmodell wie die Weboberfläche (siehe Zugriffsschutz & Löschen). Es gibt bewusst keinen Endpunkt, der alle Umfragen auflistet, da Umfragen nur über ihren Link erreichbar sein sollen.

Methode Pfad Beschreibung
POST /api/polls Neue Umfrage erstellen
GET /api/polls/{id} Umfrage inkl. Ergebnis abrufen
POST /api/polls/{id}/votes Abstimmen
DELETE /api/polls/{id} Umfrage löschen
GET /api/stats Aggregierte Nutzungsstatistik

Beispiel – Umfrage erstellen (optional mit password und durationDays, Standard/Maximum ist KONSENSOMAT_EXPIRY_DAYS):

curl -X POST http://localhost:8080/api/polls \
  -H "Content-Type: application/json" \
  -d '{"question":"Wohin geht die Firmenfeier?","options":["Strand","Berge","Stadt"],"password":"geheim","durationDays":3}'

Die Antwort enthält einmalig ein ownerToken – das solltest du dir aufheben, denn nur damit (oder als Admin) lässt sich die Umfrage später wieder löschen:

{"id":"a1b2c3","question":"...", "ownerToken":"…", "hasPassword":true, "expiresAt":1234567890, "...": "..."}

Ist eine Umfrage passwortgeschützt, muss das Passwort bei GET/votes per Header X-Poll-Password: geheim oder Query-Parameter ?password=geheim mitgeschickt werden. Für DELETE reicht das Passwort dagegen nicht aus – dafür ist immer der Owner-Token nötig (per X-Owner-Token-Header oder ?owner=-Parameter), es sei denn, ein Admin-Passwort ist konfiguriert; dieses wirkt über X-Admin-Password bzw. ?adminPassword= für beliebige Umfragen.

Beispiel – abstimmen (ein Wert je Option, 0 = kein Widerstand … 4 = starker Widerstand; comments optional, wird nur bei Wert 4 angezeigt):

curl -X POST "http://localhost:8080/api/polls/<id>/votes?password=geheim" \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","votes":[0,4,2],"comments":["","Zu weit weg",""]}'

Beispiel – löschen mit Owner-Token:

curl -X DELETE "http://localhost:8080/api/polls/<id>?owner=<ownerToken>"

Alle API-Antworten sind JSON, Fehler haben die Form {"error": "..."}, und Access-Control-Allow-Origin: * ist gesetzt, damit die API auch aus dem Browser heraus von anderen Origins angesprochen werden kann.


Mail-Funktionen

Der Mail-Versand ist vollständig optional und standardmäßig deaktiviert - er wird erst aktiv, wenn KONSENSOMAT_MAIL_ENABLED=true gesetzt ist und KONSENSOMAT_SMTP_HOST konfiguriert ist (siehe Konfiguration über .env). Der Hauptschalter KONSENSOMAT_MAIL_ENABLED ist bewusst von den SMTP-Werten getrennt: So lässt sich die Konfiguration vorbereiten und anschließend über einen einzigen Wert an- oder abschalten, ohne Host/Zugangsdaten anzufassen. Ist die Funktion deaktiviert, blenden sich sämtliche zugehörigen Formularfelder in der Weboberfläche automatisch aus, und die Routen, die Mail verschicken, lehnen die Anfrage ab.

Es gibt bewusst keine Abhängigkeit zu einem externen Mail-Anbieter: Der Versand läuft über SMTP, wahlweise gegen das Mailsystem auf demselben Server (z.B. ein lokal lauschendes Postfix, meist ohne Authentifizierung) oder gegen einen externen Smarthost/Relay (meist mit Zugangsdaten und TLS) - beides ist nur eine andere Ausprägung derselben Konfiguration.

Wiederherstellungs-Mail beim Erstellen: Trägt die Erstellerin/der Ersteller beim Anlegen einer Umfrage eine eigene E-Mail-Adresse ein, wird direkt danach einmalig eine Mail mit dem Umfrage- und dem Verwaltungslink verschickt. Das ist gedacht als Sicherheitsnetz, weil es bewusst kein Nutzerkonto und keine Passwort-Wiederherstellung gibt (siehe Zugriffsschutz & Löschen): Geht das Berechtigungs-Cookie verloren, lässt sich über diesen Link trotzdem wieder auf die Verwaltung zugreifen. Die Adresse wird zusammen mit der Umfrage gespeichert (gleiche Lebensdauer, automatische Löschung inklusive) - aktuell nur, damit eine spätere Erweiterung (z.B. eine Ablauf-Erinnerung) sie wiederverwenden könnte; sie wird sonst nirgends angezeigt oder erneut verwendet.

Basis-URL für Mail-Links: Der Umfrage-Link in beiden Mails wird standardmäßig aus Schema und Host der jeweiligen Anfrage abgeleitet (also z.B. der Adresse, mit der eine Umfrage gerade erstellt wurde). Hinter manchen Reverse-Proxy-/Load-Balancer-Konstellationen entspricht das nicht der öffentlichen Adresse - dafür lässt sich mit KONSENSOMAT_BASE_URL (z.B. https://umfrage.example.org, ohne abschließenden Slash und ohne Pfad) eine feste Basis-URL erzwingen, die für alle Mail-Links verwendet wird.

Einladung per Mail: Wer eine Umfrage verwalten darf (Erstellerin/Ersteller oder Admin), kann im Verwaltungsbereich eine Liste von E-Mail-Adressen (eine pro Zeile oder durch Komma/Semikolon getrennt, maximal 20 pro Anfrage) samt optionaler persönlicher Nachricht eintragen; jede gültige Adresse bekommt daraufhin eine Mail mit dem Umfrage-Link. Die eingetragenen Adressen werden dabei nicht gespeichert, sondern nur für den Versand dieser einen Anfrage verwendet.

Eigene Stimme später per Mail-Link anpassen: Wer eine Umfrage verwaltet, kann (beim Erstellen oder jederzeit danach im Verwaltungsbereich) einschalten, dass Abstimmende beim Abstimmen optional eine eigene E-Mail-Adresse hinterlegen können. Ist das eingeschaltet, bekommt jede Person, die dabei eine Adresse angibt, direkt danach einen individuellen Link zugeschickt, über den sie ausschließlich ihre eigene, schon abgegebene Auswahl (nicht Name oder Stimmen anderer) jederzeit erneut ändern kann - ohne Umfrage-Passwort oder Owner-Token. Der Link bleibt so lange gültig wie die Umfrage selbst und ist an ein zufälliges, nur für diese eine Stimme gültiges Token gebunden. Die Einstellung selbst sowie die hinterlegte Adresse werden zusammen mit der Umfrage gespeichert (gleiche Lebensdauer, automatische Löschung inklusive); ohne serverweit konfiguriertes Mail (KONSENSOMAT_MAIL_ENABLED=true) bleibt das E-Mail-Feld beim Abstimmen unsichtbar und eine trotzdem mitgeschickte Adresse wird ignoriert.

Alle drei Formulare (Einladung, Wiederherstellungs-Mail, E-Mail beim Abstimmen) sind gegen Missbrauch abgesichert: CSRF-Schutz, Beschränkung auf Verwaltungsberechtigte (bei Einladungen und beim Ein-/Ausschalten der Stimm-Bearbeitung), ein eigenes Rate-Limit pro Client-IP für versendete Mails sowie - für die Bearbeitungslinks - ein zeitkritisches Vergleichen des Tokens und Rate-Limiting falscher Tokens (jeweils unabhängig vom Rate-Limit für Passwort-Versuche, siehe Zugriffsschutz & Löschen).


Deployment

Statisches Binary bauen (Templates und Assets werden per go:embed eingebettet):

go build -o konsensomat .
./konsensomat

Schreibrechte für files/data vergeben (bzw. KONSENSOMAT_DATA_DIR auf ein beschreibbares Verzeichnis setzen).

Logs

Die Anwendung loggt ausschließlich nach stdout/stderr (kein Log-File) - normale Betriebsmeldungen (Start, eine Zeile pro Request mit Client-IP, Methode, Pfad, Status und Dauer) gehen nach stdout, Fehler nach stderr. Das lässt sich mit den üblichen Bordmitteln trennen bzw. einsammeln, z.B. docker compose logs -f oder ein Log-Collector, der stdout/stderr eines Containers ausliest.

Hinter einem Reverse-Proxy betreiben

KonsensOmat terminiert selbst kein TLS und ist für den Betrieb hinter einem Reverse-Proxy (nginx, Caddy, Traefik, …) ausgelegt: Der Proxy nimmt HTTPS entgegen und leitet per HTTP an KonsensOmat weiter, üblicherweise mit den Headern X-Forwarded-Proto und X-Forwarded-For.

X-Forwarded-Proto: https wird automatisch erkannt (steuert u.a. das Secure-Attribut der Cookies) - hier ist keine Konfiguration nötig.

Für X-Forwarded-For (die tatsächliche Client-IP, die Rate-Limiting und Zugriffs-Logs verwenden) muss der Proxy explizit als vertrauenswürdig eingetragen werden, per KONSENSOMAT_TRUSTED_PROXIES in der .env-Datei oder als Umgebungsvariable:

KONSENSOMAT_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8

Kommagetrennte Liste aus einzelnen IPs und/oder CIDR-Bereichen (IPv4 und IPv6). Ohne diese Angabe (Standard) wird X-Forwarded-For ignoriert und immer die direkt verbindende IP verwendet - das ist bewusst die sichere Grundeinstellung, denn dieser Header lässt sich von jedem beliebig setzen, der den Server direkt erreicht. Erst wenn eine Anfrage nachweislich von einer der eingetragenen Proxy-Adressen kommt, wird die von ihr gesetzte Client-IP übernommen (bei mehreren verketteten vertrauenswürdigen Proxies wird von rechts nach links die erste nicht selbst vertrauenswürdige Adresse verwendet).

Über Docker

cp .env.example .env      # falls noch nicht geschehen
make docker-build         # baut das Linux-Binary und das Docker-Image (konsensomat:latest)
docker compose up

docker-compose.yml bindet ./files/data ins Image, damit Umfragen einen Container-Neustart überleben, und lädt die Konfiguration aus .env (siehe Konfiguration über .env).

Das Standard-Dockerfile baut auf scratch auf (kein Betriebssystem, keine Shell - nur das Binary), läuft nicht als root. Wer stattdessen einen HEALTHCHECK braucht (z.B. für Orchestrierung), nutzt die etwas größere Alpine-Variante:

make docker-build-alpine

Weitere make-Ziele (make ohne Argument bzw. ein Blick ins Makefile zeigen alle): build-windows, build-rpi64, build-rpi32, build-mac (macOS/Apple Silicon) für andere Zielplattformen, test, run (= go run .) und clean.


Screenshots

Abb. 1: Desktop-Ansicht

Abb. 1: Desktop-Ansicht

Abb. 2: Mobile-Ansicht

Abb. 2: Mobile-Ansicht


Lizenz

GNU Affero General Public License v3 (AGPLv3). Siehe Datei LICENSE.