Erste Version

main
Samuel Zielke 1 week ago
commit 2b40254024

15
.gitignore vendored

@ -0,0 +1,15 @@
# Eigene Programme, Anweisungen und ältere Stände – gehören nicht ins Repo
privat/
td-programm-*.json
td-anweisungen-*.json
# Server-Geheimnisse
deploy/htpasswd
*.pem
*.key
# Die ausgelieferte Seite entsteht auf dem Server aus TD-Pult.html
deploy/site/*
!deploy/site/.gitkeep
.DS_Store

@ -0,0 +1,226 @@
# TD-Pult
**Ein Regiepult im Browser für die Technische Direktion bei Live-Veranstaltungen.**
TD-Pult ist eine einzelne HTML-Datei. Sie zeigt den Programmablauf einer Schicht,
den gerade laufenden Punkt, das nächste Video und eure eigenen Anweisungen an
das Team. Es gibt keinen Server, keine Datenbank und keine Installation:
Datei öffnen, Programm einpflegen, loslegen.
```
┌──────────────────────────────────────────────────────────────────────────┐
│ 14:32:07 Jahrestagung 2026 · Stadthalle [Schicht 1] ◀ ▶ Jetzt Dateien ▾│
├──────────────┬───────────────────────────────┬───────────────────────────┤
│ ABLAUF │ 14:30 VORTRAG · NR. 4 │ Disposition │ Ablaufplan │
│ 14:00 Musik │ Titel des Vortrags │ │
│▶14:30 Vortrag│ ☐ Medien & Cams vorbereitet │ Gliederung des Vortrags │
│ 14:55 Video │ ☐ Punkt erledigt │ mit Video-Cues und │
│ 15:10 Pause │ │ Lesungen │
│ │ NÄCHSTES VIDEO · in 6 Min. │ │
│ │ ① vorbereitet ② sync ③ weiter │ │
│ │ │ │
│ │ MEINE ANWEISUNGEN │ │
│ │ [Wichtig] Cam 2 auf Bühne │ │
└──────────────┴───────────────────────────────┴───────────────────────────┘
```
---
## Was das Tool kann
- **Ablauf und Jetzt-Ansicht.** Die Uhr läuft live mit. Mit *Jetzt* springt die
Ansicht zum Punkt, der laut Uhrzeit gerade dran ist.
- **Nächstes Video.** Aus Uhrzeit und Zeitleiste jedes Punkts wird geschätzt,
wann welches Video kommt. Für jedes Video gibt es drei Schritte:
*vorbereitet → sync → abgespielt*.
- **Eigene Anweisungen** pro Programmpunkt oder für die ganze Schicht, mit
Kategorien (Normal, Wichtig, Kritisch …), Abhaken und Sortieren. Bausteine und
Teamnamen lassen sich per Klick einfügen.
- **Rechte Spalte** mit Disposition (Gliederung eines Vortrags), Ablaufplan
(Medienliste und offizieller Anweisungstext) und allgemeinen Absprachen.
- **Mehrere Schichten** (z. B. Vormittag und Nachmittag), jeweils mit eigener
Besetzung.
- **Optional:** eine angeschlossene zweite Sprache (Sync-Schritt) und
übertragene Programmteile (Stream). Ist beides aus, verschwinden die
zugehörigen Kennzeichen und Schritte.
- **Läuft offline.** Nach dem Laden braucht die Seite kein Netz mehr.
---
## Schnellstart
1. `TD-Pult.html` herunterladen und im Browser öffnen (Chrome, Edge, Firefox
oder Safari).
2. Oben auf **Programm bearbeiten** klicken.
3. Unter **Einstellungen** Veranstaltung, Ort, Kategorien und Bausteine
eintragen.
4. Unter **Schicht & Besetzung** Schichten anlegen und das Team eintragen.
5. Programmpunkte links mit **+ Neuer Programmpunkt** anlegen und rechts
Disposition und Ablaufplan füllen.
6. **Programm bearbeiten** wieder ausschalten. Jetzt seid ihr im Live-Betrieb.
7. Vor der Veranstaltung **Dateien → Programm sichern** und die JSON-Datei an
alle Geräte verteilen.
Mitgeliefert ist nur ein Beispielpunkt. Alle Inhalte pflegt ihr selbst ein.
---
## Die drei Modi
| Modus | Schalter | Wofür |
|---|---|---|
| **Editieren** | `Editieren` / Taste `E` | Live-Betrieb, in dem man Anweisungen anlegen, ändern und sortieren kann |
| **Ansicht** | `Ansicht` / Taste `E` | Live-Betrieb, nur lesen und abhaken, für den ruhigen Blick während der Schicht |
| **Programm bearbeiten** | `Programm bearbeiten` | Programm, Schichten, Besetzung und Einstellungen pflegen |
### Tastenkürzel
| Taste | Wirkung |
|---|---|
| `←` `↑` / `→` `↓` | vorheriger / nächster Programmpunkt |
| `E` | zwischen Editieren und Ansicht wechseln |
| `Enter` | Anweisung speichern (`Shift`+`Enter` für einen Zeilenumbruch) |
| `Esc` | Bearbeitung einer Anweisung abbrechen |
---
## Einstellungen
Alles, was sich von Veranstaltung zu Veranstaltung unterscheidet, steht unter
**Programm bearbeiten → Einstellungen**:
| Bereich | Inhalt |
|---|---|
| Veranstaltung und Ort | Name des Tools, Veranstaltung, Ort, Stadt, Sprache |
| Angeschlossene Sprache | an/aus, Name und Kürzel (für den Sync-Schritt) |
| Stream / Übertragung | an/aus, Quelle bzw. Ort |
| Bezeichnungen | Texte der Status-Knöpfe und Medien-Schritte sowie die Begriffe *Ablaufplan*, *Vorsitz*, *Tagesmotto* und *Redner*. So passt ihr das Tool an eure Sprache an, z. B. „Moderation“ statt „Vorsitz“ |
| Kategorien | Name und Farbe der Anweisungs-Kategorien. Die Sonderrolle *Vorbereitung* koppelt eine Kategorie an den Status „vorbereitet“ |
| Bausteine | Gruppen von Stichwörtern, die als Chips unter dem Eingabefeld erscheinen (Kameras, Quellen, Schnittarten …) |
| Auswahllisten | Arten von Programmpunkten, Medienarten, Zusatzaufgaben für die Vorbereitung |
| Automatische Hinweise | „Wenn der Titel X enthält, zeige Hinweis Y.“ Der Hinweis erscheint gelb in der Jetzt-Ansicht |
---
## Eingabeformate
### Zeitleiste (Ablaufplan → Zeitleiste)
Eine Zeile pro Abschnitt, in der Reihenfolge des Vortrags:
```
Redner 2
Block 8: 1=1, 2=6
Redner 5
```
`Redner 2` sind 2 Minuten ohne Medien. `Block 8: 1=1, 2=6` ist ein Abschnitt
von 8 Minuten, in dem Video 1 (ca. 1 Min.) und Video 2 (ca. 6 Min.) laufen.
Die Nummern beziehen sich auf die Spalte *Nr.* in der Medienliste. Daraus
rechnet das Tool die ungefähren Uhrzeiten der Videos aus.
### Disposition und allgemeine Anweisungen
```
# EINLEITUNG (5 Min.)
Erster Gedanke
Unterpunkt
Unter-Unterpunkt
> [Das Video wird abgespielt (3:33)]
! [Zitat vorlesen]
```
| Zeilenanfang | Darstellung |
|---|---|
| `#` | Abschnittsüberschrift |
| `>` | Video-Cue, hervorgehoben |
| `!` | Lesung / Zitat |
| zwei Leerzeichen je Ebene | Einrückung |
In der Ansicht *kompakt* sind nur die obersten Gliederungspunkte zu sehen.
---
## Daten und Speicherung
Es gibt zwei getrennte Datenbestände. Beide liegen im **`localStorage` des
Browsers** und werden bei jeder Änderung sofort gespeichert.
| Bestand | Inhalt | Sichern / Laden |
|---|---|---|
| **Programm** | Schichten, Programmpunkte, Medien, Dispositionen, Besetzung, Einstellungen | *Dateien → Programm sichern / laden* |
| **Anweisungen** | eure Anweisungen und alle Häkchen | *Dateien → Anweisungen sichern / laden* |
So kann eine Person das Programm vorbereiten und an alle verteilen, während
jeder am Pult seine eigenen Anweisungen behält.
Gut zu wissen:
- **Jedes Gerät und jeder Browser hat seinen eigenen Stand.** Übertragen wird
per JSON-Datei.
- **Der Stand hängt an der Adresse.** Eine lokal geöffnete Datei und dieselbe
Datei auf einem Server sind für den Browser zwei verschiedene Orte.
- **Wer die Website-Daten löscht, löscht auch die Anweisungen.** Vor jeder
Schicht einmal sichern.
- **Auf Auslieferungsstand zurücksetzen** stellt den Inhalt der HTML-Datei
wieder her. Eure Anweisungen bleiben dabei erhalten.
Die exportierten Dateien heißen `td-programm-JJJJ-MM-TT.json` und
`td-anweisungen-JJJJ-MM-TT.json`. Die `.gitignore` hält sie aus dem Repo,
damit keine echten Programme oder Namen veröffentlicht werden.
### Eigenes Programm fest einbauen (optional)
Soll das Tool schon beim ersten Öffnen euer Programm zeigen, könnt ihr es in
die Datei einbetten. In `TD-Pult.html` steht in der ersten Zeile des Skripts:
```js
const SEED = { … };
```
Ersetzt das Objekt durch den Inhalt eurer exportierten `td-programm-….json`.
Speichert diese Fassung außerhalb des Repos, zum Beispiel im Ordner `privat/`,
den Git ignoriert.
---
## Auf einem Server bereitstellen
Im Ordner [`deploy/`](deploy/README.md) liegt alles, um TD-Pult per Docker und
nginx mit Passwortschutz auszuliefern, z. B. hinter dem Nginx Proxy Manager:
```bash
cd deploy
docker run --rm -it httpd:alpine htpasswd -Bn td > htpasswd
sh install.sh
```
Die ausführliche Anleitung steht in [`deploy/README.md`](deploy/README.md).
---
## Projektstruktur
```
td-pult/
├── TD-Pult.html # das komplette Tool (HTML, CSS, JavaScript)
├── README.md
├── .gitignore
├── deploy/ # optionaler Server-Betrieb mit Docker + nginx
│ ├── README.md
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── install.sh
│ └── site/ # hierhin kommt auf dem Server index.html
└── privat/ # eigene Programme und Sicherungen (nicht im Repo)
```
---
## Technik
- Eine Datei, reines HTML, CSS und JavaScript, ohne Abhängigkeiten und ohne Build
- Keine externen Aufrufe, keine Schriften oder Skripte aus dem Netz
- Daten nur im Browser (`localStorage`, Schlüssel `td-programm-v1` und `td-notizen-v1`)
- Ausgelegt für Laptops und Monitore am Regieplatz ab ca. 1280 px Breite

File diff suppressed because it is too large Load Diff

@ -0,0 +1,20 @@
# Optional: eigenständiges Image, falls du es in eine Registry schieben
# oder ohne Bind-Mounts betreiben willst.
#
# docker build -f deploy/Dockerfile -t td-pult:latest .
# docker run -d -p 8087:80 --name td-pult td-pult:latest
#
# Vor dem Bauen müssen deploy/htpasswd und deploy/site/index.html vorhanden sein.
FROM nginx:1.27-alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
COPY deploy/htpasswd /etc/nginx/.htpasswd
COPY deploy/site/ /usr/share/nginx/html/
RUN chmod 644 /etc/nginx/.htpasswd
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://127.0.0.1/healthz || exit 1
EXPOSE 80

@ -0,0 +1,170 @@
# TD-Pult im Container bereitstellen
Nginx liefert die einzelne HTML-Datei aus und schützt sie mit Basic Auth.
TLS und Domain macht dein Nginx Proxy Manager.
Alle Befehle im Ordner `deploy/` ausführen. Der Ordner `deploy/site/` enthält
die auszuliefernde Seite — dort muss `index.html` liegen, das ist die
umbenannte `TD-Pult.html`.
---
## 1 · Passwortdatei anlegen
Die Datei `htpasswd` enthält Benutzername und Passwort-Hash. Sie liegt bewusst
**nicht** im Repo und wird einmalig erzeugt:
```bash
docker run --rm httpd:alpine htpasswd -Bbn td 'DEIN-PASSWORT' > htpasswd
chmod 600 htpasswd
```
`td` ist der Benutzername, `-B` erzwingt bcrypt. Passwort in einfachen
Anführungszeichen, damit Sonderzeichen von der Shell nicht angefasst werden.
Wenn es nicht im Shell-Verlauf landen soll:
```bash
docker run --rm -it httpd:alpine htpasswd -Bn td > htpasswd # fragt interaktiv
```
Weiteren Benutzer ergänzen:
```bash
docker run --rm httpd:alpine htpasswd -Bbn regie 'ANDERES-PASSWORT' >> htpasswd
```
Passwort später ändern: Datei neu erzeugen, dann `docker compose restart`.
---
## 2 · Seite bereitstellen
```bash
mkdir -p site
cp ../TD-Pult.html site/index.html # oder die hochgeladene Datei dorthin kopieren
```
Anschließend die Rechte setzen — nginx läuft im Container als eigener Benutzer
und kann Dateien mit Modus 600 nicht lesen. Das äußert sich als **403 Forbidden**
nach erfolgreichem Login:
```bash
chmod 755 site
chmod 644 site/index.html
```
Gemountet wird der **Ordner** `site/`, nicht die einzelne Datei. Einzeldatei-Mounts
brechen, sobald ein Editor oder eine Synchronisierung die Datei ersetzt statt sie
zu beschreiben — und wenn die Datei beim Start fehlt, legt Docker an ihrer Stelle
stillschweigend ein Verzeichnis an. Genau das erzeugt die Meldung
*„not a directory: Are you trying to mount a directory onto a file“*.
Wenn das schon passiert ist, den leeren Ordner entfernen und neu starten:
```bash
docker rm -f td-pult
rmdir ../TD-Pult.html # der versehentlich angelegte Ordner
mkdir -p site && cp /pfad/zu/TD-Pult.html site/index.html
docker compose up -d
```
Alternativ macht `sh install.sh` diese Schritte inklusive Aufräumen und Prüfung.
Ausgeliefert wird die leere Vorlage. Euer Programm kommt anschließend im Browser
über *Dateien → Programm laden* hinein (JSON-Datei aus *Programm sichern*).
---
## 3 · Container starten
```bash
docker compose up -d
docker compose logs -f # bei Bedarf
curl -I http://localhost:8087/ # muss 401 liefern – dann greift der Schutz
```
Der Container hört auf Port **8087**.
---
## 4 · Nginx Proxy Manager einrichten
Neuer *Proxy Host*:
| Feld | Wert |
|---|---|
| Domain Names | z. B. `td.example.com` |
| Scheme | `http` |
| Forward Hostname / IP | IP des Docker-Hosts (bei Portfreigabe) oder `td-pult` (bei gemeinsamem Netz) |
| Forward Port | `8087` bzw. `80` |
| Cache Assets | **aus** – sonst siehst du Änderungen verzögert |
| Block Common Exploits | an |
| Websockets Support | aus |
Reiter *SSL*: Let's-Encrypt-Zertifikat anfordern, **Force SSL**, **HTTP/2**
und **HSTS** aktivieren. Basic Auth überträgt das Passwort sonst im Klartext.
Läuft der Proxy Manager selbst als Container, erreicht er `127.0.0.1` des Hosts
nicht. Dann entweder die Host-IP eintragen oder beide Container ins selbe
Docker-Netz hängen (siehe Kommentare in `docker-compose.yml`) und auf
`td-pult:80` zeigen.
Optional zusätzlich: in NPM unter *Access Lists* eine Liste mit Passwortschutz
anlegen und dem Proxy Host zuweisen. Der Schutz im Container bleibt trotzdem
aktiv — dann fragt es zweimal, deshalb entweder das eine oder das andere.
---
## 5 · Aktualisieren
Neue Version des Tools einspielen:
```bash
cp /pfad/zur/neuen/TD-Pult.html site/index.html
chmod 644 site/index.html
```
Mehr ist nicht nötig — der Ordner ist gemountet, nginx liest die Datei bei jeder
Anfrage neu. Ein `docker compose restart` braucht es nur, wenn du `nginx.conf`
oder `htpasswd` geändert hast.
Da `Cache-Control: no-store` gesetzt ist, lädt der Browser beim nächsten Aufruf
die neue Fassung. Wenn du eine ältere Version testen willst, einfach die Datei
tauschen — das Image bleibt unverändert.
---
## 6 · Was du wissen solltest
**Deine Daten liegen im Browser, nicht auf dem Server.** Anweisungen, Haken und
das bearbeitete Programm stehen im `localStorage` der jeweiligen Domain. Daraus
folgt:
- Jedes Gerät hat seinen eigenen Stand. Übertragung über *Dateien → Programm
sichern* bzw. *Anweisungen sichern* und auf dem anderen Gerät laden.
- Der Stand hängt an der Domain. Rufst du das Tool später unter einer anderen
Adresse auf, ist es leer — vorher sichern.
- Wer im Browser die Website-Daten löscht, löscht auch deine Anweisungen.
Vor der Schicht einmal sichern.
**Ausfall am Platz.** Die Seite läuft nach dem Laden vollständig im Browser.
Bricht das Netz weg, arbeitet der bereits geöffnete Tab normal weiter — nur ein
Neuladen geht dann nicht mehr. Halte die Datei zusätzlich lokal vor, dann kannst
du sie im Notfall per Doppelklick öffnen.
**Basic Auth** ist nur so sicher wie die Leitung. Ausschließlich über HTTPS
betreiben und `Force SSL` im Proxy setzen.
---
## Dateien
| Datei | Zweck |
|---|---|
| `docker-compose.yml` | Container-Definition, Ports, Mounts |
| `site/index.html` | Die ausgelieferte Seite — hierhin `TD-Pult.html` kopieren |
| `install.sh` | Richtet ein bzw. repariert einen fehlgeschlagenen Start |
| `nginx.conf` | Auslieferung, Basic Auth, Header, Healthcheck |
| `htpasswd` | Benutzer und Passwort-Hash — selbst erzeugen, nicht teilen |
| `Dockerfile` | Optional: eigenständiges Image für eine Registry |
| `../.gitignore` | Hält `htpasswd` und `site/index.html` aus der Versionierung |

@ -0,0 +1,40 @@
services:
td-pult:
image: nginx:1.27-alpine
container_name: td-pult
restart: unless-stopped
volumes:
# Ordner-Mount statt Einzeldatei: robuster beim Austauschen der Datei
# und Docker kann nicht versehentlich einen Ordner anlegen.
# In ./site muss eine index.html liegen (= TD-Pult.html umbenannt).
- ./site:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./htpasswd:/etc/nginx/.htpasswd:ro
# Variante A – Portfreigabe (funktioniert immer).
# Nginx Proxy Manager zeigt dann auf http://<Server-IP>:8087
ports:
- "8087:80"
# Variante B – gemeinsames Docker-Netz mit dem Proxy.
# Dann oben "ports" auskommentieren, hier das Netz des Proxys eintragen
# (Name mit docker network ls nachsehen) und im Proxy auf td-pult : 80 zeigen.
# networks:
# - proxy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
interval: 30s
timeout: 3s
retries: 3
logging:
driver: json-file
options:
max-size: "5m"
max-file: "3"
# networks:
# proxy:
# external: true

@ -0,0 +1,42 @@
#!/usr/bin/env sh
# Einrichtung / Reparatur auf dem Server. Im Ordner deploy/ ausführen: sh install.sh
set -e
cd "$(dirname "$0")"
# 1 · Reste eines fehlgeschlagenen Einzeldatei-Mounts aufräumen
docker rm -f td-pult 2>/dev/null || true
for p in ../TD-Pult.html ./TD-Pult.html; do
if [ -d "$p" ]; then echo "Räume leeren Ordner $p auf"; rmdir "$p" 2>/dev/null || true; fi
done
# 2 · Website-Ordner vorbereiten
mkdir -p site
if [ -f ../TD-Pult.html ]; then
cp ../TD-Pult.html site/index.html
fi
if [ ! -f site/index.html ]; then
echo "FEHLER: site/index.html fehlt. Lade TD-Pult.html hoch und kopiere sie:"
echo " cp /pfad/zu/TD-Pult.html site/index.html"
exit 1
fi
# 3 · Rechte setzen – nginx im Container liest als eigener Benutzer
chmod 755 site
chmod 644 site/index.html
# 4 · Passwortdatei prüfen
if [ ! -s htpasswd ]; then
echo "FEHLER: htpasswd fehlt. Einmalig anlegen:"
echo " docker run --rm httpd:alpine htpasswd -Bbn td 'DEIN-PASSWORT' > htpasswd"
exit 1
fi
chmod 600 htpasswd
# 5 · Starten und prüfen
docker compose up -d
sleep 2
echo
echo "Erwartet wird 401 Unauthorized:"
docker run --rm --network host curlimages/curl:latest -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:8087/ \
|| curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:8087/ \
|| echo "(Prüfung übersprungen – bitte manuell: curl -I http://127.0.0.1:8087/)"

@ -0,0 +1,48 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Echte Besucher-IP aus dem Reverse Proxy übernehmen (Docker-Netze)
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# ---- Passwortschutz -------------------------------------------------
auth_basic "TD-Pult";
auth_basic_user_file /etc/nginx/.htpasswd;
# ---- Kein Caching: eine neue HTML-Datei ist sofort aktiv ------------
add_header Cache-Control "no-store, must-revalidate" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
gzip on;
gzip_types text/plain application/json; # text/html macht nginx immer
gzip_min_length 1024;
client_max_body_size 2m;
# Statusabfrage für den Healthcheck – ohne Passwort
location = /healthz {
auth_basic off;
access_log off;
default_type text/plain;
return 200 "ok\n";
}
location / {
try_files $uri $uri/ /index.html;
}
# Versteckte Dateien nie ausliefern
location ~ /\. {
deny all;
}
}
Loading…
Cancel
Save

Powered by TurnKey Linux.