Diagnoseseite: findet und behebt haengende Zwischenspeicher

Rueckmeldung: "garnichts laedt in der verwaltungsseite".

Geprueft statt geraten -- die Serverseite ist in Ordnung:
  Dienst aktiv, keine Fehler im Protokoll
  CORS-Vorabanfrage 204 mit allow-origin
  echte Anfrage 401 (korrekt ohne Anmeldung), Antwortzeit unauffaellig
  ausgelieferte Seite enthaelt den neuen Code

Damit bleibt fast nur der Browser: ein alter Service Worker, der
veraltete Dateien ausliefert. Er ueberlebt ein normales Neuladen, und
niemand kann ihn ohne Entwicklerwerkzeuge sehen.

webdesign/diagnose.html prueft sechs Dinge und sagt im Klartext, welches
davon klemmt: Service Worker aktiv? Zwischenspeicher gefuellt? Server
erreichbar und wie schnell? Kennt der Server die neuen Adressen
(404 = Server veraltet)? Liegt eine Anmeldung vor? Und wird die AKTUELLE
Gestaltungsdatei ausgeliefert -- erkennbar an einem Merkmal, das es erst
seit heute gibt.

Ein Knopf meldet den Service Worker ab, loescht die Zwischenspeicher und
laedt die Verwaltung mit Zeitstempel neu. Der Text sagt ausdruecklich,
dass Anmeldung und Daten unberuehrt bleiben -- sonst traut sich niemand
zu klicken.

ZWEI ENTSCHEIDUNGEN

Die Seite laedt KEINE externen Dateien, alles steht inline. Sie muss
funktionieren, wenn genau das kaputt ist, was sie untersucht -- eine
Diagnoseseite, die an derselben veralteten CSS-Datei scheitert, ist
wertlos.

Sie ist von der Zugangswand ausgenommen, wie Widerruf und Rechtstexte.
Gebraucht wird sie genau dann, wenn etwas klemmt; dann darf nicht
ausgerechnet die Zugangswand davorstehen. Sie enthaelt keine Kunden-
oder Projektdaten, sondern prueft nur den eigenen Browser und meldet
Statusnummern.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-23 17:10:54 +02:00
co-authored by Claude Opus 5
parent d68d10d002
commit dd4010e75d
2 changed files with 233 additions and 0 deletions
+5
View File
@@ -445,6 +445,11 @@ export function webdesignGate(req, res, next) {
pfad === "/webdesign/zugang.html" ||
pfad === "/webdesign/widerruf.html" ||
pfad === "/webdesign/rechtliches.html" ||
/* Die Diagnoseseite bleibt ebenfalls offen. Sie wird genau dann
gebraucht, wenn etwas klemmt -- und dann darf nicht ausgerechnet
die Zugangswand dazwischenstehen. Sie enthaelt keine Daten,
sondern prueft nur den eigenen Browser und meldet Statusnummern. */
pfad === "/webdesign/diagnose.html" ||
pfad === "/webdesign/sw.js" ||
pfad === "/webdesign/app.webmanifest"
) {
+228
View File
@@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Diagnose — Dogfather Webdesign</title>
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070b" />
<style>
/* ---------------------------------------------------------------------
BEWUSST OHNE EXTERNE DATEIEN.
Diese Seite muss auch dann funktionieren, wenn genau das kaputt ist,
was sie untersuchen soll: veraltete CSS- und JS-Dateien aus dem
Zwischenspeicher. Würde sie webdesign.css oder wd-core.js laden,
könnte sie am selben Problem scheitern und wäre nutzlos.
Deshalb steht hier alles inline und es gibt keinen Service Worker.
--------------------------------------------------------------------- */
:root { color-scheme: dark; }
* { box-sizing: border-box; }
body {
margin: 0; padding: 1.5rem 1rem 3rem;
background: #05070b; color: #eef4fb;
font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
.hu { max-width: 720px; margin: 0 auto; }
h1 { font-size: 1.5rem; margin: 0 0 .3rem; }
p.lead { color: #8f9fb4; margin: 0 0 1.6rem; }
h2 { font-size: 1.05rem; margin: 1.8rem 0 .6rem; color: #7fd0e8; }
.zeile {
display: flex; gap: .8rem; align-items: flex-start;
padding: .8rem .9rem; margin-bottom: .5rem;
border: 1px solid rgba(127,208,232,.16); border-radius: 10px;
background: rgba(16,26,41,.5);
}
.zeile b { display: block; margin-bottom: .1rem; }
.zeile span { color: #8f9fb4; font-size: .9rem; word-break: break-word; }
.mark { flex: none; width: 26px; height: 26px; border-radius: 50%;
display: grid; place-items: center; font-size: .95rem; line-height: 1; }
.gut { background: rgba(111,214,163,.15); color: #6fd6a3; }
.warn { background: rgba(232,200,119,.18); color: #e8c877; }
.bad { background: rgba(255,157,157,.15); color: #ff9d9d; }
.wart { background: rgba(143,159,180,.15); color: #8f9fb4; }
button {
font: inherit; font-weight: 700; cursor: pointer;
padding: .85rem 1.3rem; border-radius: 10px; border: 0;
background: linear-gradient(135deg, #a9e2f2, #7fd0e8); color: #04060a;
min-height: 48px;
}
button.zweit { background: transparent; color: #7fd0e8; border: 1px solid rgba(127,208,232,.34); }
.knoepfe { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
.hinweis {
margin-top: 1.6rem; padding: 1rem 1.1rem; border-radius: 12px;
background: rgba(232,200,119,.08); border: 1px solid rgba(232,200,119,.32);
}
.hinweis b { color: #e8c877; }
code { background: rgba(127,208,232,.1); padding: .1rem .35rem; border-radius: 5px; font-size: .88em; }
</style>
</head>
<body>
<div class="hu">
<h1>Diagnose</h1>
<p class="lead">
Diese Seite prüft, warum der Verwaltungs- oder Portalbereich nicht lädt.
Sie lädt selbst keine externen Dateien — sie funktioniert also auch dann,
wenn genau die das Problem sind.
</p>
<h2>Prüfungen</h2>
<div id="ergebnis">
<div class="zeile"><span class="mark wart">…</span><div><b>Läuft</b><span>Einen Moment.</span></div></div>
</div>
<div class="knoepfe">
<button type="button" id="reparieren">Zwischenspeicher leeren und neu laden</button>
<button type="button" class="zweit" id="nochmal">Nochmal prüfen</button>
</div>
<div class="hinweis">
<p style="margin:0 0 .4rem"><b>Was der grosse Knopf macht</b></p>
<p style="margin:0;font-size:.92rem">
Er meldet den Service Worker ab und löscht alle zwischengespeicherten
Dateien dieser Seite. Danach holt der Browser alles frisch vom Server.
<strong>Deine Anmeldung und deine Daten bleiben unberührt</strong> —
gelöscht werden nur Kopien von Dateien, die ohnehin jederzeit neu
geladen werden können.
</p>
</div>
</div>
<script>
/* Alles inline und ohne Abhängigkeiten — siehe Begründung im CSS-Kopf. */
(function () {
"use strict";
var API = "https://postfach.dogfather-universe.com";
var ziel = document.getElementById("ergebnis");
function zeile(art, titel, text) {
var z = { gut: "✓", warn: "!", bad: "✕", wart: "…" };
var d = document.createElement("div");
d.className = "zeile";
d.innerHTML = '<span class="mark ' + art + '">' + z[art] + "</span><div><b></b><span></span></div>";
d.querySelector("b").textContent = titel;
d.querySelector("span").textContent = text;
ziel.appendChild(d);
}
async function pruefen() {
ziel.innerHTML = "";
/* 1. Service Worker — der häufigste Grund für "es lädt die alte
Fassung". Er überlebt ein normales Neuladen. */
if ("serviceWorker" in navigator) {
try {
var regs = await navigator.serviceWorker.getRegistrations();
var meine = regs.filter(function (r) {
return (r.scope || "").indexOf("/webdesign") >= 0;
});
if (!meine.length) {
zeile("gut", "Service Worker", "Keiner aktiv — Dateien kommen direkt vom Server.");
} else {
zeile("warn", "Service Worker aktiv",
meine.length + " registriert. Er kann alte Dateien ausliefern. "
+ "Der Knopf unten meldet ihn ab.");
}
} catch (e) {
zeile("warn", "Service Worker", "Konnte nicht geprüft werden: " + e.message);
}
} else {
zeile("gut", "Service Worker", "Von diesem Browser nicht unterstützt — kein Problem.");
}
/* 2. Zwischenspeicher */
if ("caches" in window) {
try {
var namen = await caches.keys();
var meineC = namen.filter(function (n) { return n.indexOf("webdesign") >= 0; });
if (!meineC.length) zeile("gut", "Zwischenspeicher", "Leer.");
else zeile("warn", "Zwischenspeicher", "Enthält: " + meineC.join(", "));
} catch (e) {
zeile("warn", "Zwischenspeicher", "Nicht prüfbar: " + e.message);
}
}
/* 3. Ist der Server erreichbar? Ein Endpunkt, der ohne Anmeldung
antwortet -- 401 ist hier die RICHTIGE Antwort und beweist,
dass alles bis dorthin funktioniert. */
try {
var t0 = Date.now();
var a = await fetch(API + "/webdesign/admin/anfragen", { cache: "no-store" });
var ms = Date.now() - t0;
if (a.status === 401) {
zeile("gut", "Server erreichbar", "Antwortet in " + ms + " ms (Status 401 = erwartet ohne Anmeldung).");
} else if (a.status === 404) {
zeile("bad", "Server veraltet", "Status 404 — der interne Dienst kennt diese Adresse noch nicht.");
} else {
zeile("warn", "Server antwortet", "Status " + a.status + " nach " + ms + " ms.");
}
} catch (e) {
zeile("bad", "Server NICHT erreichbar",
"Der Browser kommt nicht an " + API + " heran. " + (e.message || ""));
}
/* 4. Die neuen Adressen */
try {
var b = await fetch(API + "/webdesign/admin/einstellungen", { cache: "no-store" });
if (b.status === 401) zeile("gut", "Zahlungen-Bereich", "Vorhanden und geschützt.");
else if (b.status === 404) zeile("bad", "Zahlungen-Bereich fehlt",
"Der Server muss noch aktualisiert werden (git pull + Neustart).");
else zeile("warn", "Zahlungen-Bereich", "Status " + b.status + ".");
} catch (e) {
zeile("bad", "Zahlungen-Bereich", "Nicht erreichbar: " + (e.message || ""));
}
/* 5. Anmeldung im Browser vorhanden? */
try {
var tok = sessionStorage.getItem("wd-vw-token");
if (tok) zeile("gut", "Anmeldung Verwaltung", "Ein Sitzungsschlüssel liegt vor.");
else zeile("warn", "Anmeldung Verwaltung",
"Keiner vorhanden — die Verwaltung fragt nach dem Zugangscode. Das ist normal, "
+ "wenn die Seite zwischendurch geschlossen war.");
} catch (e) {
zeile("warn", "Anmeldung", "Speicher nicht lesbar: " + e.message);
}
/* 6. Welche Fassung liegt im Browser? */
try {
var c = await fetch("/assets/css/webdesign.css?diagnose=" + Date.now(), { cache: "no-store" });
var txt = await c.text();
var hatNeu = txt.indexOf("--wd-blau-rgb") >= 0;
zeile(hatNeu ? "gut" : "bad", "Gestaltungsdatei",
hatNeu ? "Aktuelle Fassung wird ausgeliefert."
: "VERALTETE Fassung. Der Knopf unten hilft.");
} catch (e) {
zeile("warn", "Gestaltungsdatei", "Nicht prüfbar: " + e.message);
}
}
document.getElementById("nochmal").addEventListener("click", pruefen);
document.getElementById("reparieren").addEventListener("click", async function () {
var k = this;
k.disabled = true;
k.textContent = "Wird bereinigt …";
try {
if ("serviceWorker" in navigator) {
var regs = await navigator.serviceWorker.getRegistrations();
for (var i = 0; i < regs.length; i++) await regs[i].unregister();
}
if ("caches" in window) {
var namen = await caches.keys();
for (var j = 0; j < namen.length; j++) await caches.delete(namen[j]);
}
} catch (e) { /* weiter -- neu laden hilft meistens trotzdem */ }
k.textContent = "Fertig — wird neu geladen …";
/* Mit Zeitstempel, damit auch der Browser-eigene Zwischenspeicher
umgangen wird. */
setTimeout(function () {
location.href = "/webdesign/verwaltung.html?frisch=" + Date.now();
}, 700);
});
pruefen();
})();
</script>
</body>
</html>