Vermittlungsserver: coturn mit Zugangsdaten, die verfallen
Damit Anrufe auch in Netzen zustande kommen, die keine direkte Verbindung zulassen (15-25 % der Faelle). coturn ist installiert, steht aber still, bis die Konfiguration liegt -- ein coturn mit Werkseinstellung ist ein offenes Relais. KEIN FESTES PASSWORT. Es laege dauerhaft im Browser jedes Team-Mitglieds und liesse sich nie entziehen. Der Server rechnet stattdessen bei jeder Abfrage Zugangsdaten, die nach zwoelf Stunden verfallen (server/workspace-turn.js, coturns `use-auth-secret`). Das Geheimnis liegt in einer Datei, nicht in der Datenbank: einstellung- Setzen() schreibt Werte ins Protokoll, und coturn braucht denselben Wert ohnehin in /etc. Die Oberflaeche frischt die Daten vor jedem Anruf auf. Der Chat ist eine App, die tagelang offen bleibt -- wer nur beim Laden holt, telefoniert am zweiten Tag ohne Vermittlung, und es faellt nicht auf: Es scheitern nur die, die sie gebraucht haetten. DIE WICHTIGSTE ZEILE DER KONFIGURATION ist die Sperrliste. Gemessen: dreizehn Dienste lauschen auf diesem Server nur oertlich, darunter Caddys Verwaltung auf 127.0.0.1:2019 -- wer sie erreicht, kann jede Website umleiten. Ohne Sperrliste waere der Vermittlungsserver die Tuer dorthin, und die Anfrage saehe fuer Caddy aus wie von localhost. Geprueft: pruef-anruf.mjs 51 -> 73 Pruefungen. Gegenprobe (Geheimnis als Passwort ausliefern + fremde Zugangsdaten ueberschreiben) macht genau 5 rot, darunter "das Geheimnis steht NIRGENDS in der Antwort". tools/turn-probelauf.sh beweist am echten coturn, was ein fester Vergleichswert nicht kann: dass coturn unsere Rechnung akzeptiert. Beide Seiten koennten sonst konsequent falsch rechnen und jede Pruefung waere gruen. Seine eigene Gegenprobe war zweimal zu Recht rot -- der erste Aufbau mass wegen `-y` gar nicht das Ziel, das er zu messen behauptete, sondern coturns eingebauten Loopback-Schutz. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# =====================================================================
|
||||
# coturn - Vermittlungsserver fuer die Anrufe im Creator Workspace
|
||||
# Angelegt 18.09.2026. Gehoert nach /etc/turnserver.conf
|
||||
#
|
||||
# WARUM DIESE DATEI IM REPO LIEGT
|
||||
# Sie steht auf dem Server unter /etc und waere bei einem Neuaufbau weg,
|
||||
# ohne dass es jemandem auffiele - der Anruf wuerde dann einfach bei
|
||||
# jedem Fuenften nicht mehr klappen. Genau das ist bei RunOne mit den
|
||||
# systemd-Dateien passiert; seitdem liegen solche Dateien versioniert.
|
||||
#
|
||||
# DAS GEHEIMNIS STEHT NICHT HIER DRIN.
|
||||
# Es wird vom Einrichtungsbefehl erzeugt und unten angehaengt. Eine
|
||||
# Vorlage mit Platzhalter waere eine Vorlage, in der irgendwann der
|
||||
# Platzhalter stehen bleibt - im Haus schon zweimal passiert.
|
||||
# =====================================================================
|
||||
|
||||
# --- Wo gelauscht wird -------------------------------------------------
|
||||
# Ausdruecklich nur auf der oeffentlichen Adresse. Ohne diese Zeile
|
||||
# lauscht coturn auf ALLEN Netzgeraeten - auch auf den vier
|
||||
# Docker-Bruecken (172.17-172.20). Ein Vermittlungsserver, der im
|
||||
# Docker-Netz erreichbar ist, ist eine Abkuerzung, die niemand braucht.
|
||||
listening-ip=159.195.212.167
|
||||
relay-ip=159.195.212.167
|
||||
listening-port=3478
|
||||
|
||||
# Kein TLS in dieser Stufe: Dafuer braeuchte es ein Zertifikat und damit
|
||||
# einen eigenen Namen im DNS. Ohne diese beiden Zeilen versucht coturn
|
||||
# trotzdem, einen TLS-Lauscher zu oeffnen, und schreibt bei jedem Start
|
||||
# eine Warnung ueber fehlende Zertifikate - eine Warnung, die immer
|
||||
# kommt, liest nach drei Tagen niemand mehr.
|
||||
no-tls
|
||||
no-dtls
|
||||
|
||||
# --- Der Portbereich fuer die Weiterleitung ----------------------------
|
||||
# Jedes weitergeleitete Gespraech belegt einen Port. Der Standard geht
|
||||
# von 49152 bis 65535 - 16000 Ports, die alle in der Firewall offen sein
|
||||
# muessten. Fuer ein Team dieser Groesse sind 100 reichlich: Sie
|
||||
# reichen fuer 100 gleichzeitige Weiterleitungen, und die Firewall
|
||||
# bleibt entsprechend eng.
|
||||
min-port=49160
|
||||
max-port=49260
|
||||
|
||||
# --- Wer den Server benutzen darf --------------------------------------
|
||||
# Keine Benutzerliste, sondern ein gemeinsames Geheimnis: Der Workspace
|
||||
# rechnet daraus Zugangsdaten, die nach zwoelf Stunden verfallen
|
||||
# (server/workspace-turn.js). coturn rechnet dieselbe Zahl nach.
|
||||
# Vorteil gegenueber festen Benutzern: Es gibt nichts abzugleichen, und
|
||||
# ein Zugang, der nicht mehr gelten soll, gilt am naechsten Tag nicht
|
||||
# mehr.
|
||||
use-auth-secret
|
||||
realm=dogfather-universe.com
|
||||
|
||||
# Wie lange eine einmal ausgestellte Anmeldung nachwirkt.
|
||||
stale-nonce=600
|
||||
|
||||
# --- DIE WICHTIGSTEN ZEILEN DER GANZEN DATEI ---------------------------
|
||||
# Ein TURN-Server leitet auf Zuruf Verkehr an eine Adresse weiter, die
|
||||
# der Anrufer nennt. Ohne die folgenden Sperren darf er auch 127.0.0.1
|
||||
# nennen - und dann steht dieser Server als Tuer vor allem, was auf
|
||||
# diesem Rechner nur oertlich lauscht. Am 18.09.2026 gemessen: dreizehn
|
||||
# Dienste, darunter
|
||||
#
|
||||
# 127.0.0.1:2019 die Verwaltung von Caddy (!) - wer sie erreicht,
|
||||
# kann JEDE Website auf diesem Server umleiten
|
||||
# 127.0.0.1:3000 Gitea 127.0.0.1:4000 VanVans Shop
|
||||
# 127.0.0.1:4100 Workspace 127.0.0.1:4400 Buchhaltung
|
||||
# 127.0.0.1:25 der Mailserver
|
||||
#
|
||||
# Fuer Caddy saehe eine so weitergeleitete Anfrage aus, als kaeme sie
|
||||
# von diesem Rechner selbst. Diese Liste ist deshalb keine Haertung,
|
||||
# sondern die Voraussetzung dafuer, dass der Dienst ueberhaupt laufen
|
||||
# darf.
|
||||
no-multicast-peers
|
||||
denied-peer-ip=0.0.0.0-0.255.255.255
|
||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
||||
denied-peer-ip=100.64.0.0-100.127.255.255
|
||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||
denied-peer-ip=169.254.0.0-169.254.255.255
|
||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
||||
denied-peer-ip=192.0.0.0-192.0.0.255
|
||||
denied-peer-ip=192.0.2.0-192.0.2.255
|
||||
denied-peer-ip=192.88.99.0-192.88.99.255
|
||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
||||
denied-peer-ip=198.18.0.0-198.19.255.255
|
||||
denied-peer-ip=198.51.100.0-198.51.100.255
|
||||
denied-peer-ip=203.0.113.0-203.0.113.255
|
||||
denied-peer-ip=240.0.0.0-255.255.255.255
|
||||
# Dasselbe fuer IPv6 - sonst waere die Sperre oben eine halbe Sperre.
|
||||
denied-peer-ip=::1
|
||||
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
|
||||
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
|
||||
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
|
||||
# Weiterleitung nur fuer UDP. WebRTC braucht nichts anderes; TCP-Ziele
|
||||
# waeren nur fuer jemanden interessant, der etwas anderes vorhat.
|
||||
no-tcp-relay
|
||||
|
||||
# --- Grenzen gegen Missbrauch ------------------------------------------
|
||||
# Falls Zugangsdaten doch einmal abhandenkommen, kostet es Bandbreite,
|
||||
# keine Rechnung ohne Ende. 12 gleichzeitige Weiterleitungen je Person
|
||||
# reichen fuer eine Vierergruppe mit Ton und Bild deutlich.
|
||||
user-quota=12
|
||||
total-quota=100
|
||||
max-bps=3000000
|
||||
|
||||
# --- Die eingebaute Fernsteuerung bleibt aus ---------------------------
|
||||
# coturn oeffnet sonst auf 127.0.0.1:5766 eine Verwaltungskonsole. Sie
|
||||
# wird hier nicht gebraucht, und ein offener Verwaltungszugang, den
|
||||
# niemand benutzt, faellt auch niemandem auf.
|
||||
no-cli
|
||||
|
||||
# --- Protokoll ---------------------------------------------------------
|
||||
# In das Systemprotokoll statt in eine eigene Datei: Damit wird es
|
||||
# mitgedreht und laeuft nicht voll. Bewusst OHNE `verbose` - ein
|
||||
# ausfuehrliches Protokoll eines Vermittlungsservers ist eine Liste,
|
||||
# wer wann mit wem telefoniert hat.
|
||||
syslog
|
||||
simple-log
|
||||
|
||||
# Hilft Gegenstellen, die Pakete zuzuordnen; kostet nichts.
|
||||
fingerprint
|
||||
|
||||
# --- Das gemeinsame Geheimnis wird unten angehaengt --------------------
|
||||
+161
-1
@@ -16,7 +16,7 @@
|
||||
|
||||
Aufrufen mit: node server/pruef-anruf.mjs
|
||||
===================================================================== */
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { mkdtempSync, rmSync, writeFileSync, unlinkSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { request as httpAnfrage } from "node:http";
|
||||
@@ -28,6 +28,11 @@ process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
||||
process.env.PORT = String(PORT);
|
||||
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
||||
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
||||
/* Der Vermittlungsserver holt sein Geheimnis aus einer Datei. Hier muss
|
||||
der Pfad UMGEBOGEN werden, bevor index.js geladen wird -- sonst
|
||||
zeigte er auf die echte Datei unter /etc, und die Pruefung wuerde
|
||||
entweder am echten Betrieb messen oder gar nichts sehen koennen. */
|
||||
process.env.TURN_GEHEIMNIS_DATEI = join(ordner, "turn.geheimnis");
|
||||
|
||||
const express = (await import("express")).default;
|
||||
const ec = express.response.cookie;
|
||||
@@ -268,6 +273,161 @@ melde("=== 6. Die Verbindungsadressen ===");
|
||||
"danach ist die Liste wieder leer -- der naechste Abschnitt faengt sauber an");
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
6b. DIE ZUGANGSDATEN ZUM VERMITTLUNGSSERVER
|
||||
|
||||
Hier steht die Frage, an der sich entscheidet, ob coturn ein Dienst
|
||||
fuer uns ist oder eine offene Weiterleitung fuer jeden: Das
|
||||
Geheimnis, aus dem die Zugangsdaten gerechnet werden, darf den
|
||||
Browser NIE erreichen. Wer es haette, koennte sich selbst beliebig
|
||||
viele Zugaenge ausstellen -- dauerhaft, ohne Konto, ueber unsere
|
||||
IP-Adresse.
|
||||
|
||||
Deshalb wird hier nicht nur geprueft, dass Zugangsdaten ANKOMMEN,
|
||||
sondern vor allem, dass das Geheimnis es NICHT tut -- und zwar im
|
||||
rohen Antworttext, nicht im ausgewerteten Objekt. Ein Feld, an das
|
||||
niemand denkt, faellt beim Auswerten nicht auf.
|
||||
======================================================================= */
|
||||
melde("");
|
||||
melde("=== 6b. Zugangsdaten fuer die Vermittlung ===");
|
||||
{
|
||||
const turn = await import("./workspace-turn.js");
|
||||
const datei = process.env.TURN_GEHEIMNIS_DATEI;
|
||||
/* Erfunden, nur fuer diesen Lauf, und am Ende wieder weg. Lang genug,
|
||||
dass die Laengenpruefung nicht zufaellig anschlaegt. */
|
||||
const GEHEIM = "pruef-geheimnis-nur-fuer-diesen-lauf-0123456789";
|
||||
|
||||
/* --- Die drei Ausgaenge beim Lesen ------------------------------- */
|
||||
{
|
||||
const g = turn.turnGeheimnisLesen(datei);
|
||||
ok(g.wert === "" && g.grund === "fehlt",
|
||||
`ohne Datei sagt es "fehlt" statt stillschweigend nichts (${g.grund})`);
|
||||
}
|
||||
{
|
||||
writeFileSync(datei, "zu-kurz");
|
||||
const g = turn.turnGeheimnisLesen(datei);
|
||||
ok(g.wert === "" && g.grund === "zu_kurz",
|
||||
`ein zu kurzes Geheimnis wird abgelehnt, nicht benutzt (${g.grund})`);
|
||||
}
|
||||
{
|
||||
writeFileSync(datei, " \n");
|
||||
const g = turn.turnGeheimnisLesen(datei);
|
||||
ok(g.wert === "" && g.grund === "leer", `eine leere Datei meldet "leer" (${g.grund})`);
|
||||
}
|
||||
{
|
||||
writeFileSync(datei, GEHEIM + "\n");
|
||||
const g = turn.turnGeheimnisLesen(datei);
|
||||
ok(g.wert === GEHEIM && g.grund === "",
|
||||
"mit Datei kommt das Geheimnis an (Zeilenumbruch abgeschnitten)");
|
||||
}
|
||||
|
||||
/* --- Ein WECHSEL muss auffallen ---------------------------------- */
|
||||
{
|
||||
writeFileSync(datei, GEHEIM + "-zweite-fassung-mit-anderer-laenge");
|
||||
const g = turn.turnGeheimnisLesen(datei);
|
||||
ok(g.wert.endsWith("-zweite-fassung-mit-anderer-laenge"),
|
||||
"wird die Datei getauscht, wird der neue Wert gelesen (kein Festhaengen)");
|
||||
writeFileSync(datei, GEHEIM + "\n");
|
||||
}
|
||||
|
||||
/* --- Die Rechnung ------------------------------------------------ */
|
||||
{
|
||||
/* Eine FESTE Zeit als Eingabe, nicht die Wanduhr: Sonst waere die
|
||||
Pruefung eine Zeitbombe, die irgendwann etwas anderes misst. */
|
||||
const JETZT = 1_700_000_000_000;
|
||||
const z = turn.turnZugang("w7", GEHEIM, JETZT);
|
||||
const [ablaufStr, kennung] = z.username.split(":");
|
||||
ok(kennung === "w7", `im Benutzernamen steht die Kennung (${kennung})`);
|
||||
ok(Number(ablaufStr) === Math.floor(JETZT / 1000) + turn.TURN_GUELTIG_STUNDEN * 3600,
|
||||
`und davor der Ablauf, genau ${turn.TURN_GUELTIG_STUNDEN} Stunden spaeter`);
|
||||
|
||||
/* FESTER VERGLEICHSWERT, ausgerechnet und nicht behauptet. Er
|
||||
beweist NICHT, dass coturn ihn akzeptiert -- das beweist nur ein
|
||||
Versuch am laufenden coturn (turnutils_uclient). Er beweist,
|
||||
dass sich die Rechenvorschrift nicht unbemerkt aendert; genau
|
||||
das wuerde jeden Anruf ueber die Vermittlung scheitern lassen,
|
||||
ohne dass irgendwo ein Fehler steht. */
|
||||
ok(z.credential === "9AbMBJeTDL6uebcR6KRpYVTFwt8=",
|
||||
`das Passwort ist die bekannte HMAC-SHA1-Rechnung (${z.credential})`);
|
||||
|
||||
const nochmal = turn.turnZugang("w7", GEHEIM, JETZT);
|
||||
ok(nochmal.credential === z.credential, "dieselbe Eingabe ergibt dasselbe Passwort");
|
||||
const anderer = turn.turnZugang("w8", GEHEIM, JETZT);
|
||||
ok(anderer.credential !== z.credential,
|
||||
"eine andere Person bekommt ein anderes Passwort");
|
||||
const spaeter = turn.turnZugang("w7", GEHEIM, JETZT + 60_000);
|
||||
ok(spaeter.credential !== z.credential,
|
||||
"und eine Minute spaeter ein anderes -- die Daten verfallen wirklich");
|
||||
}
|
||||
|
||||
/* --- Wer bekommt Zugangsdaten und wer nicht ---------------------- */
|
||||
{
|
||||
const liste = [
|
||||
{ urls: "stun:turn.dogfather-universe.com:3478" },
|
||||
{ urls: "turn:turn.dogfather-universe.com:3478" },
|
||||
{ urls: "turn:fremder-dienst.example:3478", username: "fremd", credential: "eigenes" },
|
||||
];
|
||||
const e = turn.mitZugangsdaten(liste, "w7", 1_700_000_000_000, datei);
|
||||
ok(!e.adressen[0].credential, "ein STUN-Eintrag bekommt keine Zugangsdaten");
|
||||
ok(!!e.adressen[1].credential && e.adressen[1].username.includes(":w7"),
|
||||
"unser TURN-Eintrag bekommt gerechnete");
|
||||
ok(e.adressen[2].credential === "eigenes" && e.adressen[2].username === "fremd",
|
||||
"ein fremder Dienst behaelt seine eigenen -- wir ueberschreiben nichts");
|
||||
ok(e.turn_gesamt === 2 && e.turn_bereit === 2,
|
||||
`und die Zahlen stimmen (${e.turn_bereit} von ${e.turn_gesamt} bereit)`);
|
||||
}
|
||||
{
|
||||
/* OHNE Geheimnis: Der Eintrag bleibt stehen, aber es wird gesagt. */
|
||||
unlinkSync(datei);
|
||||
const e = turn.mitZugangsdaten(
|
||||
[{ urls: "turn:turn.dogfather-universe.com:3478" }], "w7", Date.now(), datei);
|
||||
ok(e.turn_gesamt === 1 && e.turn_bereit === 0 && e.grund === "fehlt",
|
||||
`ohne Geheimnis ist nichts bereit, und der Grund steht dabei (${e.grund})`);
|
||||
writeFileSync(datei, GEHEIM + "\n");
|
||||
}
|
||||
|
||||
/* --- Und jetzt ueber die Schnittstelle, wie der Browser sie sieht - */
|
||||
{
|
||||
await roh("/workspace/api/anruf/adressen", HAUS, { cookie: kDogi }, {
|
||||
adressen: [{ urls: "turn:turn.dogfather-universe.com:3478" }],
|
||||
}, "PUT");
|
||||
const a = await roh("/workspace/api/anruf/adressen", CREW, { cookie: kModi });
|
||||
const r = json(a);
|
||||
|
||||
ok(r.turn_gesamt === 1 && r.turn_bereit === 1,
|
||||
`der Modi bekommt einen einsatzbereiten Eintrag (${r.turn_bereit}/${r.turn_gesamt})`);
|
||||
ok(!!r.adressen?.[0]?.credential && !!r.adressen?.[0]?.username,
|
||||
"mit Benutzername und Passwort");
|
||||
|
||||
/* DIE WICHTIGSTE PRUEFUNG DES ABSCHNITTS. Im ROHEN Text gesucht,
|
||||
nicht im ausgewerteten Objekt. */
|
||||
ok(!a.text.includes(GEHEIM),
|
||||
"und das Geheimnis selbst steht NIRGENDS in der Antwort");
|
||||
|
||||
/* Jeder bekommt EIGENE Zugangsdaten -- im coturn-Protokoll steht
|
||||
damit die Nummer, nicht ein Sammelkonto. */
|
||||
const rDogi = json(await roh("/workspace/api/anruf/adressen", HAUS, { cookie: kDogi }));
|
||||
ok(rDogi.adressen?.[0]?.username !== r.adressen?.[0]?.username,
|
||||
"DogFather und der Modi bekommen verschiedene Zugangsdaten");
|
||||
|
||||
/* Der Ablauf muss fuer den Browser LESBAR sein -- darauf beruht das
|
||||
Auffrischen vor jedem Anruf in anruf.js. */
|
||||
const ablauf = Number(String(r.adressen?.[0]?.username || "").split(":")[0]);
|
||||
ok(ablauf > Math.floor(Date.now() / 1000) + 3600,
|
||||
`der Ablauf steht vorn im Benutzernamen und liegt in der Zukunft (${ablauf})`);
|
||||
}
|
||||
|
||||
/* --- Aufraeumen: der naechste Abschnitt faengt sauber an ---------- */
|
||||
{
|
||||
await roh("/workspace/api/anruf/adressen", HAUS, { cookie: kDogi },
|
||||
{ adressen: [] }, "PUT");
|
||||
try { unlinkSync(datei); } catch { /* schon weg */ }
|
||||
const zurueck = json(await roh("/workspace/api/anruf/adressen", HAUS, { cookie: kDogi }));
|
||||
ok(zurueck.adressen?.length === 0 && !zurueck.turn_grund,
|
||||
"danach ist wieder alles leer -- keine erfundene Adresse bleibt stehen");
|
||||
}
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
7. IM BROWSER
|
||||
======================================================================= */
|
||||
|
||||
@@ -72,6 +72,7 @@ import {
|
||||
istDogFather,
|
||||
} from "./workspace.js";
|
||||
import { chatEreignis, istDrinFuerAnruf, teilnehmerFuerAnruf } from "./workspace-chat.js";
|
||||
import { mitZugangsdaten } from "./workspace-turn.js";
|
||||
|
||||
export const anrufRouter = express.Router();
|
||||
|
||||
@@ -161,13 +162,38 @@ anrufRouter.get("/workspace/api/anruf/adressen", (req, res) => {
|
||||
try {
|
||||
let liste = [];
|
||||
try { liste = JSON.parse(einstellung("anruf_adressen") || "[]"); } catch { liste = []; }
|
||||
|
||||
/* ZUGANGSDATEN WERDEN GERECHNET, NICHT GESPEICHERT.
|
||||
Warum das so ist, steht ausfuehrlich in workspace-turn.js. Kurz:
|
||||
Ein festes TURN-Passwort laege dauerhaft im Browser jedes
|
||||
Teilnehmers und liesse sich nie wieder entziehen. Die hier
|
||||
erzeugten verfallen von selbst.
|
||||
|
||||
Als Kennung die PERSONENNUMMER, nicht der Name -- sie landet im
|
||||
Protokoll von coturn, und das ist kein Ort fuer Klarnamen. */
|
||||
const { adressen, turn_gesamt, turn_bereit, grund } =
|
||||
mitZugangsdaten(liste, `w${req.person.id}`);
|
||||
|
||||
if (grund) {
|
||||
/* DER DRITTE AUSGANG. „Kann nicht nachsehen" ist etwas anderes
|
||||
als „ist nicht eingerichtet" -- und wer das verschweigt, sucht
|
||||
den Fehler spaeter beim Anrufer. */
|
||||
console.warn(`[anruf] TURN-Geheimnis nicht verwendbar: ${grund}`);
|
||||
}
|
||||
|
||||
res.json({
|
||||
adressen: Array.isArray(liste) ? liste : [],
|
||||
adressen,
|
||||
gruppe_max: GRUPPE_MAX,
|
||||
/* WAS DIE OBERFLAECHE WISSEN MUSS, um ehrlich zu sein: Ohne
|
||||
Adressen klappt der Anruf nur im selben Netz. Das gehoert
|
||||
DAVOR gesagt, nicht als Fehlermeldung danach. */
|
||||
nur_gleiches_netz: !Array.isArray(liste) || liste.length === 0,
|
||||
/* Zahlen statt Ja/Nein: Wer zaehlen kann, kann pruefen. Ein
|
||||
eingetragener TURN-Server, der mangels Geheimnis niemanden
|
||||
durchlaesst, sieht sonst genauso aus wie ein funktionierender. */
|
||||
turn_gesamt,
|
||||
turn_bereit,
|
||||
...(grund ? { turn_grund: grund } : {}),
|
||||
});
|
||||
} catch (f) {
|
||||
console.error("[anruf] Adressen:", f?.message);
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/* =====================================================================
|
||||
ZUGANGSDATEN FÜR DEN VERMITTLUNGSSERVER (TURN) -- 18.09.2026
|
||||
|
||||
Ein TURN-Server leitet Ton und Bild weiter, wenn zwei Browser sich
|
||||
nicht direkt erreichen (strenge Firmennetze, manche Mobilfunknetze).
|
||||
Er will wissen, wer ihn benutzen darf -- also Benutzer und Passwort.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM HIER GERECHNET WIRD, STATT EIN PASSWORT EINZUTRAGEN
|
||||
|
||||
Der naheliegende Weg wäre ein festes Passwort in den Einstellungen.
|
||||
Es wäre in zwei Minuten fertig -- und es läge danach dauerhaft im
|
||||
Browser jedes Team-Mitglieds, sichtbar in jedem Netzwerk-Fenster,
|
||||
gültig ohne Ende. Wer einmal dabei war, könnte den Server für immer
|
||||
als Weiterleitung benutzen: fremden Verkehr über unsere IP-Adresse
|
||||
schicken, unsere Bandbreite verbrauchen. Ein Zugang, den man nicht
|
||||
entziehen kann, ist kein Zugang, sondern ein Loch.
|
||||
|
||||
Deshalb der Weg, den coturn selbst dafür vorsieht
|
||||
(`use-auth-secret`): Der Server hier kennt ein GEHEIMNIS, das den
|
||||
Browser nie erreicht. Aus ihm rechnet er bei jeder Abfrage frische
|
||||
Zugangsdaten, die nach TURN_GUELTIG_STUNDEN von selbst verfallen.
|
||||
coturn rechnet dieselbe Zahl nach und kennt dafür nur das Geheimnis
|
||||
-- es muss keine Benutzerliste geben, und nichts muss abgeglichen
|
||||
werden.
|
||||
|
||||
Benutzername = <Ablaufzeitpunkt>:<Kennung>
|
||||
Passwort = base64( HMAC-SHA1( Geheimnis, Benutzername ) )
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM DAS GEHEIMNIS IN EINER DATEI LIEGT UND NICHT IN DER DATENBANK
|
||||
|
||||
Zwei Gründe, beide im Haus schon einmal teuer gewesen:
|
||||
|
||||
1. `einstellungSetzen()` schreibt den WERT in das Protokoll
|
||||
(`detail: "schluessel = wert"`). Ein Geheimnis dort wäre im
|
||||
Klartext in der Prüfspur -- und in jeder nächtlichen Sicherung.
|
||||
|
||||
2. coturn braucht denselben Wert in `/etc/turnserver.conf`. Eine
|
||||
Datei, die beide Seiten aus derselben Quelle bekommen, kann nicht
|
||||
auseinanderlaufen; zwei Eingabefelder können es immer.
|
||||
|
||||
Die Datei gehört root und ist für die Gruppe des Dienstes lesbar
|
||||
(640). Der Browser bekommt sie nie zu sehen -- das ist der Punkt,
|
||||
an dem `pruef-anruf.mjs` nachmisst.
|
||||
===================================================================== */
|
||||
|
||||
import { createHmac } from "node:crypto";
|
||||
import { readFileSync, statSync } from "node:fs";
|
||||
|
||||
/** Wie lange erzeugte Zugangsdaten gelten.
|
||||
*
|
||||
* Zwölf Stunden ist die Spanne, die einen Arbeitstag überdeckt, ohne
|
||||
* dass jemand mittendrin herausfliegt -- und kurz genug, dass ein
|
||||
* Zugang, der nicht mehr gelten soll, am nächsten Tag nicht mehr
|
||||
* gilt. Die Oberfläche holt die Adressen zusätzlich bei jedem Anruf
|
||||
* neu; deshalb ist diese Zahl eine Obergrenze, kein Takt. */
|
||||
export const TURN_GUELTIG_STUNDEN = 12;
|
||||
|
||||
/** Wo das gemeinsame Geheimnis liegt.
|
||||
*
|
||||
* Über eine Umgebungsvariable umstellbar -- nicht aus Vorliebe für
|
||||
* Einstellbarkeit, sondern weil die Prüfung sonst entweder gar nicht
|
||||
* messen könnte oder an die echte Datei müsste. */
|
||||
export const TURN_GEHEIMNIS_DATEI =
|
||||
process.env.TURN_GEHEIMNIS_DATEI || "/etc/coturn-workspace.geheimnis";
|
||||
|
||||
/* Gelesenes Geheimnis, gemerkt über einen ÄNDERUNGSSTEMPEL statt über
|
||||
eine Laufzeit: Wer die Datei austauscht, soll nicht bis zum Ablauf
|
||||
einer Frist warten. Ein `stat` je Abfrage kostet nichts, und die
|
||||
Abfrage kommt ein paar Mal am Tag, nicht ein paar Mal je Sekunde. */
|
||||
let gemerkt = { stempel: "", wert: "" };
|
||||
|
||||
/** Liest das Geheimnis.
|
||||
*
|
||||
* DREI AUSGÄNGE, nicht zwei -- „ich kann nicht nachsehen" ist etwas
|
||||
* anderes als „es ist keins da". Der Grund wandert bis in die Antwort
|
||||
* der Schnittstelle, damit an der Oberfläche nicht geraten wird.
|
||||
*
|
||||
* @returns {{wert: string, grund: string}} `grund` ist leer, wenn
|
||||
* alles in Ordnung ist; sonst `fehlt`, `leer`, `zu_kurz` oder
|
||||
* `unlesbar:<CODE>`.
|
||||
*/
|
||||
export function turnGeheimnisLesen(datei = TURN_GEHEIMNIS_DATEI) {
|
||||
let s;
|
||||
try {
|
||||
s = statSync(datei);
|
||||
} catch (f) {
|
||||
gemerkt = { stempel: "", wert: "" };
|
||||
return { wert: "", grund: f?.code === "ENOENT" ? "fehlt" : `unlesbar:${f?.code || "?"}` };
|
||||
}
|
||||
const stempel = `${s.mtimeMs}:${s.size}`;
|
||||
if (stempel !== gemerkt.stempel) {
|
||||
try {
|
||||
gemerkt = { stempel, wert: readFileSync(datei, "utf8").trim() };
|
||||
} catch (f) {
|
||||
gemerkt = { stempel: "", wert: "" };
|
||||
return { wert: "", grund: `unlesbar:${f?.code || "?"}` };
|
||||
}
|
||||
}
|
||||
if (!gemerkt.wert) return { wert: "", grund: "leer" };
|
||||
/* Ein zu kurzes Geheimnis ist schlimmer als keins: Es sieht aus wie
|
||||
Sicherheit und ist in Minuten geraten. 32 Zeichen entsprechen den
|
||||
16 Bytes, die der Einrichtungsbefehl erzeugt. */
|
||||
if (gemerkt.wert.length < 32) return { wert: "", grund: "zu_kurz" };
|
||||
return { wert: gemerkt.wert, grund: "" };
|
||||
}
|
||||
|
||||
/** Rechnet ein Paar Zugangsdaten aus.
|
||||
*
|
||||
* @param kennung Wer fragt -- landet im Benutzernamen und damit im
|
||||
* Protokoll von coturn. Keine Namen, nur die Nummer: Das Protokoll
|
||||
* eines Vermittlungsservers ist kein Ort für Klarnamen.
|
||||
* @param geheimnis Der gemeinsame Wert aus der Datei.
|
||||
* @param jetzt Millisekunden. Als Eingabe übergeben, nicht aus der
|
||||
* Wanduhr gelesen -- sonst wäre die Prüfung eine Zeitbombe.
|
||||
*/
|
||||
export function turnZugang(kennung, geheimnis, jetzt = Date.now()) {
|
||||
const ablauf = Math.floor(jetzt / 1000) + TURN_GUELTIG_STUNDEN * 3600;
|
||||
const username = `${ablauf}:${kennung}`;
|
||||
return {
|
||||
username,
|
||||
credential: createHmac("sha1", geheimnis).update(username).digest("base64"),
|
||||
ablauf,
|
||||
};
|
||||
}
|
||||
|
||||
/** Ergänzt eine Adressliste um frische Zugangsdaten.
|
||||
*
|
||||
* WELCHER EINTRAG BEKOMMT WELCHE BEHANDLUNG:
|
||||
*
|
||||
* stun:… nichts -- STUN kennt keine Anmeldung.
|
||||
* turn:… ohne eigenes Passwort gerechnete Zugangsdaten (unser
|
||||
* coturn).
|
||||
* turn:… MIT username+credential unverändert -- das ist ein
|
||||
* fremder Dienst, den jemand bewusst
|
||||
* eingetragen hat. Wir überschreiben
|
||||
* keine Zugangsdaten, die wir nicht
|
||||
* ausgestellt haben.
|
||||
*
|
||||
* @returns {{adressen: object[], turn_gesamt: number,
|
||||
* turn_bereit: number, grund: string}}
|
||||
* Die ZAHLEN gehören zurückgegeben, nicht nur die Liste: Eine
|
||||
* Prüfung, die „ist grün" sagen soll, muss zählen können, worüber
|
||||
* sie redet -- sonst ist sie auch bei einer leeren Liste grün.
|
||||
*/
|
||||
export function mitZugangsdaten(liste, kennung, jetzt = Date.now(),
|
||||
datei = TURN_GEHEIMNIS_DATEI) {
|
||||
const rein = Array.isArray(liste) ? liste : [];
|
||||
const brauchtZugang = (e) =>
|
||||
/^turns?:/.test(String(e?.urls || "")) && !e?.credential;
|
||||
const turn_gesamt = rein.filter((e) => /^turns?:/.test(String(e?.urls || ""))).length;
|
||||
|
||||
if (!rein.some(brauchtZugang)) {
|
||||
return { adressen: rein, turn_gesamt, turn_bereit: turn_gesamt, grund: "" };
|
||||
}
|
||||
|
||||
const { wert, grund } = turnGeheimnisLesen(datei);
|
||||
if (!wert) {
|
||||
/* KEIN STILLES WEGLASSEN. Ohne Zugangsdaten weist coturn jede
|
||||
Anfrage ab -- der Anruf fällt dann auf die direkte Verbindung
|
||||
zurück und scheitert genau bei den Leuten, für die der Server
|
||||
gebaut wurde. Der Eintrag bleibt trotzdem in der Liste: Ein
|
||||
`turn:` ohne Passwort schadet nicht, und der Grund steht dabei. */
|
||||
return {
|
||||
adressen: rein,
|
||||
turn_gesamt,
|
||||
turn_bereit: rein.filter((e) => /^turns?:/.test(String(e?.urls || "")) && e?.credential).length,
|
||||
grund,
|
||||
};
|
||||
}
|
||||
|
||||
const zugang = turnZugang(kennung, wert, jetzt);
|
||||
return {
|
||||
adressen: rein.map((e) =>
|
||||
brauchtZugang(e)
|
||||
? { ...e, username: zugang.username, credential: zugang.credential }
|
||||
: e),
|
||||
turn_gesamt,
|
||||
turn_bereit: turn_gesamt,
|
||||
grund: "",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/* =====================================================================
|
||||
DEN VERMITTLUNGSSERVER EINTRAGEN UND NACHMESSEN -- 18.09.2026
|
||||
|
||||
Aufrufen (auf dem Server, als Benutzer dogiweb):
|
||||
|
||||
node tools/turn-einrichten.mjs pruefen
|
||||
node tools/turn-einrichten.mjs eintragen turn:159.195.212.167:3478
|
||||
node tools/turn-einrichten.mjs entfernen
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM EIN BEFEHL UND KEIN EINGABEFELD
|
||||
|
||||
Die Adresse wird einmal eingetragen und dann jahrelang nicht mehr.
|
||||
Ein Menuepunkt dafuer waere eine Seite, die niemand findet, wenn er
|
||||
sie braucht -- und eine, die gepflegt werden muss. Der Befehl steht
|
||||
dagegen neben den anderen Einrichtungsschritten, in derselben
|
||||
Anleitung.
|
||||
|
||||
WAS DIESER BEFEHL NICHT TUT: Er erzeugt das Geheimnis nicht. Das
|
||||
gehoert root, weil es auch in /etc/turnserver.conf muss. Hier wird
|
||||
nur NACHGESEHEN, ob es aus Sicht des Dienstes lesbar ist -- der
|
||||
haeufigste Fehler beim Einrichten sind Dateirechte, und der faellt
|
||||
sonst erst beim ersten Anruf auf, der nicht zustande kommt.
|
||||
|
||||
DAS GEHEIMNIS WIRD NIE AUSGEGEBEN, auch nicht gekuerzt.
|
||||
===================================================================== */
|
||||
|
||||
import { createSocket } from "node:dgram";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { turnGeheimnisLesen, TURN_GEHEIMNIS_DATEI } from "../server/workspace-turn.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const DB_PFAD = process.env.WORKSPACE_DB
|
||||
|| join(__dirname, "..", "..", "workspace-daten", "workspace.db");
|
||||
|
||||
const [, , befehl, wert] = process.argv;
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
EIN ECHTER VERSUCH, KEIN BLICK IN DIE KONFIGURATION
|
||||
|
||||
Eine Konfigurationsdatei zu lesen beweist, dass jemand etwas
|
||||
hingeschrieben hat. Ob der Dienst laeuft, ob die Firewall offen ist
|
||||
und ob er von aussen antwortet, beweist nur eine Anfrage.
|
||||
|
||||
Ein STUN-Binding ist dafuer die kleinste moegliche: 20 Byte hin,
|
||||
und zurueck kommt die eigene Adresse, wie der Server sie sieht.
|
||||
--------------------------------------------------------------------- */
|
||||
function stunFragen(host, port = 3478, frist = 3000) {
|
||||
return new Promise((fertig) => {
|
||||
const kennung = randomBytes(12);
|
||||
const anfrage = Buffer.alloc(20);
|
||||
anfrage.writeUInt16BE(0x0001, 0); // Binding Request
|
||||
anfrage.writeUInt16BE(0x0000, 2); // keine Attribute
|
||||
anfrage.writeUInt32BE(0x2112a442, 4); // der feste Erkennungswert
|
||||
kennung.copy(anfrage, 8);
|
||||
|
||||
const s = createSocket("udp4");
|
||||
const uhr = setTimeout(() => {
|
||||
try { s.close(); } catch { /* schon zu */ }
|
||||
fertig({ ok: false, grund: "keine Antwort innerhalb von 3 Sekunden" });
|
||||
}, frist);
|
||||
|
||||
s.on("error", (f) => {
|
||||
clearTimeout(uhr);
|
||||
try { s.close(); } catch { /* schon zu */ }
|
||||
fertig({ ok: false, grund: f.message });
|
||||
});
|
||||
|
||||
s.on("message", (a) => {
|
||||
clearTimeout(uhr);
|
||||
try { s.close(); } catch { /* schon zu */ }
|
||||
if (a.length < 20 || a.readUInt16BE(0) !== 0x0101) {
|
||||
return fertig({ ok: false, grund: "Antwort ist kein STUN-Erfolg" });
|
||||
}
|
||||
if (!a.subarray(8, 20).equals(kennung)) {
|
||||
return fertig({ ok: false, grund: "Antwort gehoert zu einer anderen Anfrage" });
|
||||
}
|
||||
/* XOR-MAPPED-ADDRESS heraussuchen -- die Adresse ist mit dem
|
||||
Erkennungswert verodert, damit unterwegs niemand daran
|
||||
herumbastelt. */
|
||||
let p = 20;
|
||||
while (p + 4 <= a.length) {
|
||||
const art = a.readUInt16BE(p), laenge = a.readUInt16BE(p + 2);
|
||||
if (art === 0x0020 && laenge >= 8) {
|
||||
const hafen = a.readUInt16BE(p + 6) ^ 0x2112;
|
||||
const ip = [0, 1, 2, 3]
|
||||
.map((i) => a[p + 8 + i] ^ [0x21, 0x12, 0xa4, 0x42][i]).join(".");
|
||||
return fertig({ ok: true, gesehenAls: `${ip}:${hafen}` });
|
||||
}
|
||||
p += 4 + laenge + ((4 - (laenge % 4)) % 4);
|
||||
}
|
||||
fertig({ ok: true, gesehenAls: "(ohne Adressangabe)" });
|
||||
});
|
||||
|
||||
s.send(anfrage, port, host, (f) => {
|
||||
if (f) {
|
||||
clearTimeout(uhr);
|
||||
try { s.close(); } catch { /* schon zu */ }
|
||||
fertig({ ok: false, grund: f.message });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function adressenLesen(db) {
|
||||
try {
|
||||
const r = db.prepare("SELECT wert FROM einstellungen WHERE schluessel = 'anruf_adressen'").get();
|
||||
const l = JSON.parse(r?.wert || "[]");
|
||||
return Array.isArray(l) ? l : [];
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
function adressenSchreiben(db, liste) {
|
||||
db.prepare(`
|
||||
INSERT INTO einstellungen (schluessel, wert, geaendert, von)
|
||||
VALUES ('anruf_adressen', ?, ?, NULL)
|
||||
ON CONFLICT(schluessel) DO UPDATE SET
|
||||
wert = excluded.wert, geaendert = excluded.geaendert`)
|
||||
.run(JSON.stringify(liste), new Date().toISOString());
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
if (!["pruefen", "eintragen", "entfernen"].includes(befehl)) {
|
||||
console.log("Aufruf: node tools/turn-einrichten.mjs pruefen"
|
||||
+ "\n node tools/turn-einrichten.mjs eintragen turn:159.195.212.167:3478"
|
||||
+ "\n node tools/turn-einrichten.mjs entfernen");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const db = new DatabaseSync(DB_PFAD);
|
||||
|
||||
if (befehl === "eintragen") {
|
||||
if (!/^(stun|turn|turns):[^\s]+$/.test(wert || "")) {
|
||||
console.error("FEHLT: eine Adresse wie turn:159.195.212.167:3478");
|
||||
process.exit(2);
|
||||
}
|
||||
const alt = adressenLesen(db);
|
||||
/* Doppelte Eintraege vermeiden -- zweimal dieselbe Adresse bedeutet,
|
||||
dass jeder Browser sie zweimal probiert. */
|
||||
const neu = alt.filter((e) => e.urls !== wert).concat([{ urls: wert }]);
|
||||
adressenSchreiben(db, neu);
|
||||
console.log(`Eingetragen. Vorher ${alt.length}, jetzt ${neu.length} Adresse(n).`);
|
||||
} else if (befehl === "entfernen") {
|
||||
adressenSchreiben(db, []);
|
||||
console.log("Alle Adressen entfernt.");
|
||||
}
|
||||
|
||||
/* ---------- Der Zustand, immer ---------------------------------------- */
|
||||
console.log("");
|
||||
console.log("=== Zustand ===");
|
||||
|
||||
const adressen = adressenLesen(db);
|
||||
console.log(`Eingetragene Adressen: ${adressen.length}`);
|
||||
for (const a of adressen) console.log(` ${a.urls}`);
|
||||
|
||||
const g = turnGeheimnisLesen();
|
||||
console.log("");
|
||||
if (g.wert) {
|
||||
console.log(`Geheimnis: lesbar (${g.wert.length} Zeichen) aus ${TURN_GEHEIMNIS_DATEI}`);
|
||||
} else {
|
||||
console.log(`Geheimnis: NICHT verwendbar -- ${g.grund}`);
|
||||
console.log(` erwartet unter ${TURN_GEHEIMNIS_DATEI}`);
|
||||
if (g.grund.startsWith("unlesbar")) {
|
||||
console.log(" (Dateirechte: root:dogiweb mit 640)");
|
||||
}
|
||||
}
|
||||
|
||||
/* Wirklich anfragen -- bei jeder eingetragenen Adresse. */
|
||||
let erreichbar = 0, versucht = 0;
|
||||
for (const a of adressen) {
|
||||
const m = /^(?:stun|turns?):([^:/?]+)(?::(\d+))?/.exec(a.urls || "");
|
||||
if (!m) continue;
|
||||
versucht++;
|
||||
const antwort = await stunFragen(m[1], Number(m[2] || 3478));
|
||||
if (antwort.ok) {
|
||||
erreichbar++;
|
||||
console.log(`\n${a.urls}: antwortet. Von dort aus sehen wir aus wie ${antwort.gesehenAls}`);
|
||||
} else {
|
||||
console.log(`\n${a.urls}: KEINE ANTWORT -- ${antwort.grund}`);
|
||||
console.log(" Meist eins von dreien: Dienst laeuft nicht "
|
||||
+ "(systemctl status coturn), Firewall zu (ufw), oder falsche Adresse.");
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
console.log("");
|
||||
if (!adressen.length) {
|
||||
console.log("ERGEBNIS: Keine Adresse eingetragen -- Anrufe klappen nur im selben Netz.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!g.wert) {
|
||||
console.log("ERGEBNIS: Adresse da, aber ohne Geheimnis weist coturn jeden ab.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (versucht && erreichbar < versucht) {
|
||||
console.log(`ERGEBNIS: ${erreichbar} von ${versucht} Adressen antworten.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("ERGEBNIS: Eingetragen, Geheimnis lesbar, Server antwortet.");
|
||||
@@ -0,0 +1,186 @@
|
||||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# BEWEIST, DASS COTURN UNSERE ZUGANGSDATEN AKZEPTIERT -- 18.09.2026
|
||||
#
|
||||
# Aufrufen auf dem Server: bash tools/turn-probelauf.sh
|
||||
#
|
||||
# ---------------------------------------------------------------------
|
||||
# WARUM ES DIESES SKRIPT GIBT
|
||||
#
|
||||
# `pruef-anruf.mjs` prueft die Rechenvorschrift gegen einen festen
|
||||
# Vergleichswert. Das schuetzt davor, dass sie sich unbemerkt aendert --
|
||||
# es beweist aber NICHT, dass coturn das Ergebnis akzeptiert. Wir
|
||||
# koennten beide Seiten konsequent falsch rechnen, und jede Pruefung
|
||||
# waere gruen, waehrend kein einziger Anruf ueber die Vermittlung
|
||||
# zustande kaeme. Das faellt im Betrieb nicht auf: Es scheitern nur die
|
||||
# 15-25 %, die den Server ueberhaupt brauchen.
|
||||
#
|
||||
# Hier laeuft deshalb ein echter coturn und ein echter Client.
|
||||
#
|
||||
# ---------------------------------------------------------------------
|
||||
# DASS ES NICHTS ANFASST, IST ABSICHT
|
||||
#
|
||||
# Eigenes Heimatverzeichnis, eigener Port, nur 127.0.0.1, kein root,
|
||||
# kein Systemdienst. Der laufende coturn auf 3478 bleibt unberuehrt,
|
||||
# und von aussen ist der Probe-Server nicht erreichbar.
|
||||
# =====================================================================
|
||||
set -u
|
||||
P="${TMPDIR:-/tmp}/turn-probe-$$"
|
||||
FEHLER=0
|
||||
ok() { echo " ok $1"; }
|
||||
fehl() { echo " FEHL $1"; FEHLER=$((FEHLER+1)); }
|
||||
|
||||
aufraeumen() { pkill -f "$P/turn.conf" 2>/dev/null; kill "${GEGENPART:-0}" 2>/dev/null; sleep 1; rm -rf "$P"; }
|
||||
trap aufraeumen EXIT
|
||||
|
||||
command -v turnserver >/dev/null || { echo "KANN NICHT NACHSEHEN: coturn ist nicht installiert."; exit 3; }
|
||||
command -v turnutils_uclient >/dev/null || { echo "KANN NICHT NACHSEHEN: turnutils_uclient fehlt."; exit 3; }
|
||||
NODE=$(command -v node || echo /usr/local/bin/node)
|
||||
[ -x "$NODE" ] || { echo "KANN NICHT NACHSEHEN: node nicht gefunden."; exit 3; }
|
||||
|
||||
mkdir -p "$P"
|
||||
|
||||
# ZIEL FUER DEN SPERRTEST -- und warum ausgerechnet dieses.
|
||||
# Der erste Anlauf nahm 127.0.0.1. Die Gegenprobe (Abschnitt 4) zeigte:
|
||||
# Auch OHNE Sperrliste kam 403 -- coturn verbietet Loopback-Ziele von
|
||||
# sich aus. Der Abschnitt haette also NICHT unsere Sperrliste gemessen,
|
||||
# sondern eine eingebaute Regel, und waere gruen geblieben, wenn jemand
|
||||
# die Liste entfernt. Genau dafuer ist die Gegenprobe da.
|
||||
# Die Docker-Bruecke ist ein echtes oertliches Ziel auf diesem Server
|
||||
# und KEIN Loopback -- ohne unsere Liste waere sie erreichbar.
|
||||
ZIEL="${TURN_PROBE_ZIEL:-172.17.0.1}"
|
||||
|
||||
# WARUM HIER KEIN `-y` STEHT (und im ersten Anlauf stand):
|
||||
# `-y` heisst "client-to-client" -- der Client verbindet dann zwei
|
||||
# EIGENE Kanaele miteinander und ignoriert `-e` vollstaendig. Das Ziel
|
||||
# war damit immer die Relay-Adresse 127.0.0.1, also Loopback, und das
|
||||
# sperrt coturn von sich aus. Die Gegenprobe blieb deshalb zweimal rot,
|
||||
# ohne dass an der Sperrliste etwas falsch war. Erst die Hilfeseite des
|
||||
# Werkzeugs hat es gezeigt -- nicht das dritte Herumprobieren.
|
||||
# Jetzt laeuft ein echter Gegenpart auf der Docker-Bruecke.
|
||||
gegenpart_starten() {
|
||||
turnutils_peer -L "$ZIEL" -p 3480 >/dev/null 2>&1 &
|
||||
GEGENPART=$!
|
||||
sleep 1
|
||||
}
|
||||
gegenpart_beenden() { kill "$GEGENPART" 2>/dev/null; }
|
||||
GEHEIM=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
||||
|
||||
# Die Konfiguration entspricht deploy/turnserver.conf; abweichend nur
|
||||
# Adresse, Ports und dass das Protokoll in den Probeordner geht.
|
||||
schreibe_konf() { # $1 = "mit-sperre" oder "ohne-sperre"
|
||||
cat > "$P/turn.conf" <<CONF
|
||||
listening-ip=127.0.0.1
|
||||
relay-ip=127.0.0.1
|
||||
listening-port=34780
|
||||
no-tls
|
||||
no-dtls
|
||||
min-port=49400
|
||||
max-port=49420
|
||||
use-auth-secret
|
||||
static-auth-secret=$GEHEIM
|
||||
realm=dogfather-universe.com
|
||||
no-multicast-peers
|
||||
no-tcp-relay
|
||||
no-cli
|
||||
user-quota=12
|
||||
total-quota=100
|
||||
CONF
|
||||
if [ "$1" = "mit-sperre" ]; then
|
||||
cat >> "$P/turn.conf" <<'CONF'
|
||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
||||
CONF
|
||||
fi
|
||||
}
|
||||
|
||||
starten() {
|
||||
turnserver -c "$P/turn.conf" --no-stdout-log --log-file="$P/turn.log" >/dev/null 2>&1 &
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
ss -lnu 2>/dev/null | grep -q 34780 && return 0
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
anhalten() { pkill -f "$P/turn.conf" 2>/dev/null; sleep 1; }
|
||||
|
||||
# Dieselbe Rechnung wie server/workspace-turn.js -- bewusst nachgebaut
|
||||
# statt importiert: Wenn beide Seiten aus derselben Zeile kaemen, waere
|
||||
# ein Fehler in dieser Zeile hier unsichtbar.
|
||||
zugang() {
|
||||
"$NODE" -e "
|
||||
const {createHmac} = require('node:crypto');
|
||||
const u = (Math.floor(Date.now()/1000) + 12*3600) + ':w7';
|
||||
console.log(u + '\t' + createHmac('sha1', process.argv[1]).update(u).digest('base64'));
|
||||
" "$GEHEIM"
|
||||
}
|
||||
|
||||
echo "=== Probelauf gegen einen echten coturn ==="
|
||||
echo
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
echo "--- 1. Die Konfiguration aus deploy/turnserver.conf ---"
|
||||
schreibe_konf mit-sperre
|
||||
gegenpart_starten
|
||||
if starten; then ok "coturn startet und lauscht auf 127.0.0.1:34780"
|
||||
else fehl "coturn startet nicht -- $(tail -3 "$P/turn.log" 2>/dev/null | tr '\n' ' ')"; exit 1; fi
|
||||
|
||||
A=$(turnutils_stunclient -p 34780 127.0.0.1 2>&1)
|
||||
echo "$A" | grep -q "reflexive addr" \
|
||||
&& ok "er beantwortet eine STUN-Anfrage ($(echo "$A" | grep -m1 -o '127[0-9.:]*'))" \
|
||||
|| fehl "keine STUN-Antwort: $(echo "$A" | head -2 | tr '\n' ' ')"
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
echo
|
||||
echo "--- 2. Die Kernfrage: nimmt coturn unsere gerechneten Daten an? ---"
|
||||
L=$(zugang); U=$(echo "$L" | cut -f1); W=$(echo "$L" | cut -f2)
|
||||
RICHTIG=$(turnutils_uclient -u "$U" -w "$W" -p 34780 -n 2 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
|
||||
FALSCH=$(turnutils_uclient -u "$U" -w "nicht-das-richtige-passwort" -p 34780 -n 1 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
|
||||
|
||||
# Der Unterschied ist eindeutig und genau der richtige:
|
||||
# Mit gueltigen Daten kommt der Client UEBER die Allocation hinaus und
|
||||
# scheitert erst am gesperrten Ziel. Mit ungueltigen scheitert schon
|
||||
# die Allocation selbst.
|
||||
echo "$RICHTIG" | grep -q "Cannot complete Allocation" \
|
||||
&& fehl "unsere Zugangsdaten werden ABGEWIESEN -- kein Anruf ginge ueber die Vermittlung" \
|
||||
|| ok "unsere Zugangsdaten werden angenommen (die Zuteilung gelingt)"
|
||||
|
||||
echo "$FALSCH" | grep -q "Cannot complete Allocation" \
|
||||
&& ok "ein falsches Passwort wird abgewiesen -- die Pruefung prueft wirklich" \
|
||||
|| fehl "ein FALSCHES Passwort kam durch -- der Server steht jedem offen"
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
echo
|
||||
echo "--- 3. Die Sperrliste: kommt jemand an die oertlichen Dienste? ---"
|
||||
echo "$RICHTIG" | grep -qi "403" \
|
||||
&& ok "ein Ziel im oertlichen Netz ($ZIEL) wird verweigert (403)" \
|
||||
|| fehl "$ZIEL war als Ziel ERLAUBT -- ein Weg zu jedem oertlichen Dienst"
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
echo
|
||||
echo "--- 4. Gegenprobe: ohne Sperrliste MUSS derselbe Versuch durchgehen ---"
|
||||
echo " (sonst haette Abschnitt 3 nur gemessen, dass irgendetwas scheitert)"
|
||||
anhalten
|
||||
schreibe_konf ohne-sperre
|
||||
if starten; then
|
||||
L=$(zugang); U=$(echo "$L" | cut -f1); W=$(echo "$L" | cut -f2)
|
||||
OHNE=$(turnutils_uclient -u "$U" -w "$W" -p 34780 -n 4 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
|
||||
echo "$OHNE" | grep -qi "403" \
|
||||
&& fehl "auch ohne Sperrliste 403 -- Abschnitt 3 beweist dann nichts" \
|
||||
|| ok "ohne Sperrliste laeuft derselbe Versuch durch -- die Sperre war die Ursache"
|
||||
echo "$OHNE" | grep -qiE "total transmit|tot_send_msgs|Total transmit time" \
|
||||
&& ok "und es fliessen wirklich Daten ueber die Weiterleitung" \
|
||||
|| echo " -- Datenfluss nicht ablesbar (nicht schlimm, der Rest steht)"
|
||||
else
|
||||
fehl "zweiter Start misslungen"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "$FEHLER" -eq 0 ]; then
|
||||
echo "ALLES IN ORDNUNG -- coturn und der Workspace rechnen dasselbe."
|
||||
exit 0
|
||||
fi
|
||||
echo "NICHT IN ORDNUNG -- $FEHLER Punkt(e)."
|
||||
exit 1
|
||||
@@ -22,25 +22,25 @@
|
||||
},
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/img/app-symbole/workspace-192.png?v=202609181442",
|
||||
"src": "/assets/img/app-symbole/workspace-192.png?v=202609181521",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/app-symbole/workspace-512.png?v=202609181442",
|
||||
"src": "/assets/img/app-symbole/workspace-512.png?v=202609181521",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609181442",
|
||||
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609181521",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609181442",
|
||||
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609181521",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
|
||||
@@ -247,6 +247,10 @@
|
||||
kastenBauen();
|
||||
$('anruf').hidden = false;
|
||||
lage('Mikrofon …');
|
||||
/* Läuft NEBEN der Mikrofonfrage, nicht davor: Das Auffrischen
|
||||
kostet sonst Zeit, in der nichts passiert -- und die Frage nach
|
||||
dem Mikrofon dauert ohnehin länger als der kleine Abruf. */
|
||||
const adressenFertig = adressenAuffrischen();
|
||||
let eigener;
|
||||
try {
|
||||
eigener = await eigenenStromHolen(mitVideo);
|
||||
@@ -255,6 +259,7 @@
|
||||
setTimeout(() => { if (!anruf) $('anruf').hidden = true; }, 6000);
|
||||
return;
|
||||
}
|
||||
await adressenFertig;
|
||||
anruf = { raumId, video: mitVideo, verbindungen: new Map(),
|
||||
stroeme: new Map(), eigener, beginn: Date.now() };
|
||||
$('anruf-video').hidden = !mitVideo;
|
||||
@@ -278,6 +283,10 @@
|
||||
}
|
||||
|
||||
async function annehmen(raumId) {
|
||||
/* Auch hier auffrischen -- wer angerufen WIRD, braucht die
|
||||
Vermittlung genauso. Beide Wege einzeln abgesichert, nicht nur
|
||||
der, an den man zuerst denkt. */
|
||||
const adressenFertig = adressenAuffrischen();
|
||||
const a = await fetch(`/workspace/api/anruf/${raumId}/dabei`, {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
}).catch(() => null);
|
||||
@@ -288,6 +297,7 @@
|
||||
return;
|
||||
}
|
||||
const d = await a.json();
|
||||
await adressenFertig;
|
||||
kastenBauen();
|
||||
$('anruf').hidden = false;
|
||||
let eigener;
|
||||
@@ -500,6 +510,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Verbindungsadressen ----------------------------------------
|
||||
Die Zugangsdaten zum Vermittlungsserver VERFALLEN (sie werden vom
|
||||
Server gerechnet, nicht gespeichert -- siehe server/workspace-turn.js).
|
||||
|
||||
Diese Seite bleibt als App oft tagelang offen. Wer sie nur beim
|
||||
Laden holt, telefoniert am zweiten Tag ohne Vermittlung -- und das
|
||||
fällt nicht auf: Es scheitern nur die Anrufe derer, die sie
|
||||
gebraucht hätten. Genau dafür ist sie gebaut.
|
||||
|
||||
Deshalb vor jedem Anruf nachsehen. Der Ablaufzeitpunkt steht im
|
||||
Benutzernamen (`<ablauf>:<kennung>`) -- er lässt sich hier ablesen,
|
||||
ohne dass der Server ein zusätzliches Feld schicken muss. */
|
||||
|
||||
/* Fünf Minuten Vorlauf: Ein Anruf, der in 30 Sekunden beginnt, soll
|
||||
nicht mit Daten starten, die in 40 ablaufen. */
|
||||
const ZUGANG_VORLAUF = 300;
|
||||
let adressenGeholt = false;
|
||||
|
||||
function adressenFrisch() {
|
||||
if (!adressenGeholt) return false;
|
||||
const jetzt = Math.floor(Date.now() / 1000);
|
||||
return adressen.every((a) => {
|
||||
const m = /^(\d{10,}):/.exec(a && a.username || '');
|
||||
return !m || Number(m[1]) > jetzt + ZUGANG_VORLAUF;
|
||||
});
|
||||
}
|
||||
|
||||
async function adressenAuffrischen(erzwingen) {
|
||||
if (!erzwingen && adressenFrisch()) return;
|
||||
try {
|
||||
const a = await fetch('/workspace/api/anruf/adressen', { credentials: 'same-origin' });
|
||||
if (a.ok) {
|
||||
const d = await a.json();
|
||||
adressen = d.adressen || [];
|
||||
nurGleichesNetz = !!d.nur_gleiches_netz;
|
||||
gruppeMax = d.gruppe_max || 4;
|
||||
adressenGeholt = true;
|
||||
/* Ein eingetragener Vermittlungsserver, der niemanden
|
||||
durchlässt, ist schlimmer als keiner -- er sieht aus, als
|
||||
wäre alles eingerichtet. Deshalb steht er in der Konsole. */
|
||||
if (d.turn_grund) {
|
||||
console.warn('[anruf] Vermittlungsserver ohne Zugangsdaten:', d.turn_grund);
|
||||
}
|
||||
}
|
||||
} catch { /* dann eben ohne -- im selben Netz geht es trotzdem */ }
|
||||
}
|
||||
|
||||
/* ---------- Nach außen -------------------------------------------------- */
|
||||
|
||||
window.anrufStarten = starten;
|
||||
@@ -508,15 +565,7 @@
|
||||
window.anrufBereit = () => adressen.length > 0 || nurGleichesNetz;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const a = await fetch('/workspace/api/anruf/adressen', { credentials: 'same-origin' });
|
||||
if (a.ok) {
|
||||
const d = await a.json();
|
||||
adressen = d.adressen || [];
|
||||
nurGleichesNetz = !!d.nur_gleiches_netz;
|
||||
gruppeMax = d.gruppe_max || 4;
|
||||
}
|
||||
} catch { /* dann eben ohne -- im selben Netz geht es trotzdem */ }
|
||||
await adressenAuffrischen();
|
||||
try {
|
||||
const i = await fetch('/workspace/api/ich', { credentials: 'same-origin' });
|
||||
if (i.ok) ichId = (await i.json())?.id ?? null;
|
||||
|
||||
+12
-12
@@ -12,19 +12,19 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -291,10 +291,10 @@
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/aufgaben.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/aufgaben.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/automation.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/automation.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -168,10 +168,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/automation.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/automation.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+12
-12
@@ -7,14 +7,14 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -105,9 +105,9 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/befinden.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/befinden.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+18
-18
@@ -12,24 +12,24 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181521" />
|
||||
<!-- Wegen des Blocks „Willst du dazugehoeren?" auf dem Brett
|
||||
„Mitmachen". Eine eigene Kopie der Regel hier waere die zweite
|
||||
Stelle, an der dieselbe Sache anders aussieht. -->
|
||||
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -243,14 +243,14 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/checkliste.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/ampel.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/schulung.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/vorlagen.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereich.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/checkliste.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/ampel.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/schulung.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/vorlagen.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereich.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+15
-15
@@ -7,17 +7,17 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/treff.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/treff.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -107,10 +107,10 @@
|
||||
</main>
|
||||
|
||||
<!-- Der Fuß gehört zu jeder Seite - siehe pruef-css-klassen. -->
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/auskunft.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bewerben.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/auskunft.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bewerben.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-14
@@ -7,16 +7,16 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -80,10 +80,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/auskunft.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bewerbungen.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/auskunft.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bewerbungen.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-14
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/calls.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/calls.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -89,11 +89,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/ki.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/calls.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/ki.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/calls.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-14
@@ -12,26 +12,26 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<!-- aufgaben.css bringt die Dialog- und Formularstile mit (.dialog,
|
||||
.neu__zeile, .dialog__knoepfe). Sie stehen dort seit dem
|
||||
Aufgabenbrett; sie hierher zu kopieren hiesse, sie an zwei Stellen
|
||||
zu pflegen. Dieselbe Lösung nutzen kalender.html und content.html.
|
||||
Gemeldet von pruef-css-klassen.mjs, bevor jemand einen ungestylten
|
||||
Dialog zu sehen bekam. -->
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/chat.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/chat.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -341,15 +341,15 @@
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<script src="assets/js/bereiche.js?v=202609181442"></script>
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442"></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442"></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521"></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521"></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521"></script>
|
||||
<!-- ANRUF VOR CHAT (18.09.2026): anruf.js stellt `window.anrufStarten`
|
||||
bereit, und chat.js baut die Knoepfe nur, wenn es das gibt.
|
||||
Andersherum waere die Reihenfolge eine stille Abhaengigkeit --
|
||||
die Knoepfe fehlten, und niemand wuesste warum. -->
|
||||
<script src="assets/js/anruf.js?v=202609181442"></script>
|
||||
<script src="assets/js/chat.js?v=202609181442"></script>
|
||||
<script src="assets/js/anruf.js?v=202609181521"></script>
|
||||
<script src="assets/js/chat.js?v=202609181521"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+15
-15
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/content.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/content.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -179,12 +179,12 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/checkliste.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/vorlagen.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/content.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/checkliste.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/vorlagen.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/content.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -36,15 +36,15 @@
|
||||
<link rel="preload" as="image" href="assets/img/buehne/crew-gate-hoch-580.webp"
|
||||
type="image/webp" media="(max-width: 899px)" />
|
||||
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<!-- Diese Wand steht NUR auf der Adresse von Team Dogi (ueberall sonst
|
||||
404, siehe crewWeiche). Sie darf das Haus deshalb direkt nennen
|
||||
statt ueber den Platzhalter haus.css zu gehen -- und sie MUSS es:
|
||||
Eine Zugangswand, die sich erst nach dem Umbiegen faerbt, waere
|
||||
die eine Seite, auf der man das Umfaerben sieht. -->
|
||||
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="gate gate--crew">
|
||||
@@ -339,6 +339,6 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/gate.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/gate.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -22,25 +22,25 @@
|
||||
},
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/img/app-symbole/crew-192.png?v=202609181442",
|
||||
"src": "/assets/img/app-symbole/crew-192.png?v=202609181521",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/app-symbole/crew-512.png?v=202609181442",
|
||||
"src": "/assets/img/app-symbole/crew-512.png?v=202609181521",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609181442",
|
||||
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609181521",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609181442",
|
||||
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609181521",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
|
||||
+13
-13
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/dateien.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/dateien.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -108,10 +108,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/dateien.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/dateien.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+12
-12
@@ -7,14 +7,14 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -102,9 +102,9 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/entwicklung.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/entwicklung.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
<link rel="preload" as="image" href="assets/img/buehne/gate-hoch-580.webp"
|
||||
type="image/webp" media="(max-width: 899px)" />
|
||||
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="gate">
|
||||
@@ -401,6 +401,6 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/gate.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/gate.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-14
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/kalender.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/kalender.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -377,11 +377,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kalender.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kalender-abo.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kalender.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kalender-abo.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/leistung.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/leistung.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -444,10 +444,10 @@
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442"></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442"></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442"></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442"></script>
|
||||
<script src="assets/js/leistung.js?v=202609181442"></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521"></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521"></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521"></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521"></script>
|
||||
<script src="assets/js/leistung.js?v=202609181521"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/personen.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/personen.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -423,10 +423,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/personen.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/personen.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-14
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/profil.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/profil.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -214,11 +214,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/steckbrief.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/profil.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/steckbrief.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/profil.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+12
-12
@@ -7,14 +7,14 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/treff.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/treff.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -134,9 +134,9 @@
|
||||
Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen
|
||||
Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen,
|
||||
die seit dem 07.09. jede Seite danach absucht. -->
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/rechte.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/rechte.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-14
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/report.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/report.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -106,11 +106,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/ki.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/report.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/ki.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/report.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/scouting.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/scouting.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -173,10 +173,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/scouting.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/scouting.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+12
-12
@@ -12,21 +12,21 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<!-- Nur die Startseite: Begrüßungskachel, Uhr, Glockenmulde. Siehe
|
||||
den Hinweis in start.css -- dort stand es bis zum 07.09.2026. -->
|
||||
<link rel="stylesheet" href="assets/css/heim.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/heim.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -674,10 +674,10 @@
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/start.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/start.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-14
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -85,11 +85,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/ki.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/startcheck.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/ki.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/startcheck.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-14
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/profil.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/profil.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -146,11 +146,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/auskunft.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/steckbrief.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/auskunft.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/steckbrief.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -7,18 +7,18 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181521" />
|
||||
<!-- Wegen des Kastens „Anfragen“ ueber der Liste. Eine eigene Kopie
|
||||
der Regel hier waere die zweite Stelle, an der dieselbe Sache
|
||||
anders aussieht. -->
|
||||
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -111,9 +111,9 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/talente.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/talente.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -7,20 +7,20 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/team.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/team.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -110,10 +110,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/team.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/team.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+12
-12
@@ -7,20 +7,20 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/team.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/team.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<!-- `data-buehne` steht hier fest und nicht im Skript: Die Kachel zu
|
||||
@@ -84,9 +84,9 @@
|
||||
Kaputt ist dabei nichts -- er funktioniert. Genau deshalb faellt
|
||||
es niemandem auf, und deshalb prueft pruef-css-klassen die
|
||||
ABHAENGIGKEIT statt der Optik. Hier hat sie angeschlagen. -->
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/teamlage.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/teamlage.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+11
-11
@@ -7,13 +7,13 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -112,9 +112,9 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/teilen.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/teilen.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/treff.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/treff.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -126,9 +126,9 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/treff-moderation.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/treff-moderation.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -7,18 +7,18 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/treff.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/treff.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein. Auf der Adresse des
|
||||
Treffs biegt der Server genau diesen Dateinamen auf crew-haus.css
|
||||
um, und die Seite kommt schon beim ERSTEN Abruf in Lila und
|
||||
Babyblau an -- ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -211,10 +211,10 @@
|
||||
Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen
|
||||
Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen,
|
||||
die seit dem 07.09. jede Seite danach absucht. -->
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/auskunft.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/treff-regeln.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/auskunft.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/treff-regeln.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -12,22 +12,22 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<!-- Formular- und Dialogstile: seit dem 07.09.2026 steht hier der
|
||||
Dialog "Creator anlegen". -->
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -149,11 +149,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/uebersicht.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/uebersicht.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-13
@@ -12,20 +12,20 @@
|
||||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||||
eine Flaeche sind statt zweier. -->
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/wissen.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/wissen.css?v=202609181521" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
|
||||
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
|
||||
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
|
||||
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
|
||||
genau diesen Dateinamen auf crew-haus.css um, und die Seite
|
||||
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
|
||||
ohne Umfaerben im Browser, ohne Flackern. -->
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -190,10 +190,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wissen.js?v=202609181442" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609181521" defer></script>
|
||||
<script src="assets/js/wissen.js?v=202609181521" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user