Filipes Diagnose zeigte zwei "!", obwohl alles in Ordnung war: ! Service Worker aktiv -> 1 registriert ! Zwischenspeicher -> dogfather-webdesign-v11 ! Anmeldung Verwaltung -> keiner vorhanden Alle drei sind der NORMALFALL: Ein aktiver Service Worker ist gewollt -- ohne ihn liesse sich die Seite nicht als App installieren. Das war ausdruecklicher Wunsch. Der Zwischenspeicher enthielt v11 -- also genau die Fassung, die der Server ausliefert. Die erste Fassung meldete JEDEN gefuellten Zwischenspeicher als auffaellig, auch den topaktuellen. Das ist Panikmache, kein Befund. Die Anmeldung gilt je Tab. In einem frisch geoeffneten Tab ist zwangslaeufig keine da -- und sie SOLL beim Schliessen ohnehin verschwinden, das war eine ausdrueckliche Vorgabe. Ein Pruefwerkzeug, das den Normalzustand anmahnt, ist schlimmer als keines: Es schickt einen auf die Suche nach einem Fehler, den es nicht gibt -- und wenn dann einmal ein echter kommt, sieht er genauso aus wie das Rauschen davor. Jetzt liest die Diagnose die erwartete Fassung aus dem Service-Worker-Skript auf dem Server und VERGLEICHT. Nur eine Abweichung ist ein Befund. GEPRUEFT mit beiden Faellen: bei v11 sechsmal gruen, bei kuenstlich gesetztem v3 ein "!" mit beiden Fassungsnummern im Klartext. Co-Authored-By: Claude Opus 5 <[email protected]>
303 lines
12 KiB
HTML
303 lines
12 KiB
HTML
<!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);
|
|
}
|
|
/* min-width:0 ist Pflicht, kein Feinschliff: Ohne das weigert sich ein
|
|
Flex-Kind, unter seine Inhaltsbreite zu schrumpfen, und langer Text
|
|
sprengt die Karte seitlich. */
|
|
.zeile .inhalt { min-width: 0; flex: 1 1 auto; }
|
|
.zeile b { display: block; margin-bottom: .1rem; }
|
|
/* Nur der Beschreibungstext, NICHT jeder span in der Zeile -- sonst
|
|
traefe die Regel auch den Statuskreis. */
|
|
.zeile .text { color: #8f9fb4; font-size: .9rem; overflow-wrap: anywhere; }
|
|
.mark {
|
|
flex: 0 0 26px; width: 26px; height: 26px; border-radius: 50%;
|
|
display: grid; place-items: center;
|
|
font-size: .95rem; line-height: 1;
|
|
/* Der Kreis enthaelt genau ein Zeichen. Sollte je mehr hineingeraten,
|
|
bricht es nicht die Seite auf, sondern wird abgeschnitten. */
|
|
overflow: hidden;
|
|
}
|
|
.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" aria-hidden="true">…</span><div class="inhalt"><b>Prüfung läuft</b><span class="text">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");
|
|
|
|
/* Die Zeile wird Element fuer Element gebaut, NICHT ueber innerHTML mit
|
|
anschliessendem querySelector.
|
|
|
|
Genau daran ist die erste Fassung gescheitert: Sie schrieb
|
|
d.querySelector("span").textContent = text -- und querySelector
|
|
liefert den ERSTEN passenden span. Das war der Statuskreis, nicht das
|
|
Textfeld darunter. Der ganze Beschreibungstext landete dadurch in
|
|
einem 26 Pixel breiten Kreis und lief als schmale Buchstabensaeule
|
|
ueber die halbe Seite.
|
|
|
|
Mit direkten Verweisen auf die erzeugten Elemente kann das nicht
|
|
passieren -- es gibt nichts zu verwechseln. */
|
|
function zeile(art, titel, text) {
|
|
var zeichen = { gut: "✓", warn: "!", bad: "✕", wart: "…" };
|
|
|
|
var d = document.createElement("div");
|
|
d.className = "zeile";
|
|
|
|
var kreis = document.createElement("span");
|
|
kreis.className = "mark " + art;
|
|
kreis.textContent = zeichen[art] || "?";
|
|
kreis.setAttribute("aria-hidden", "true");
|
|
|
|
var inhalt = document.createElement("div");
|
|
inhalt.className = "inhalt";
|
|
|
|
var ueberschrift = document.createElement("b");
|
|
ueberschrift.textContent = titel;
|
|
|
|
var beschreibung = document.createElement("span");
|
|
beschreibung.className = "text";
|
|
beschreibung.textContent = text;
|
|
|
|
inhalt.appendChild(ueberschrift);
|
|
inhalt.appendChild(beschreibung);
|
|
d.appendChild(kreis);
|
|
d.appendChild(inhalt);
|
|
ziel.appendChild(d);
|
|
}
|
|
|
|
async function pruefen() {
|
|
ziel.innerHTML = "";
|
|
|
|
/* Welche Fassung SOLL es sein? Steht im Service-Worker-Skript auf
|
|
dem Server. Ohne diesen Vergleich kann man nicht sagen, ob ein
|
|
gefuellter Zwischenspeicher gut oder schlecht ist -- die erste
|
|
Fassung meldete jeden Zwischenspeicher als "auffaellig", auch den
|
|
topaktuellen. Das ist Panikmache, kein Befund. */
|
|
var sollFassung = null;
|
|
try {
|
|
var swTxt = await (await fetch("/webdesign/sw.js?diagnose=" + Date.now(), { cache: "no-store" })).text();
|
|
var tref = swTxt.match(/CACHE_NAME\s*=\s*"([^"]+)"/);
|
|
if (tref) sollFassung = tref[1];
|
|
} catch (e) { /* nicht schlimm, dann eben ohne Vergleich */ }
|
|
|
|
/* 1. Service Worker.
|
|
|
|
Aktiv zu sein ist der NORMALFALL -- die Seite ist als App
|
|
installierbar, dafuer braucht es ihn. Auffaellig ist er nur, wenn
|
|
er eine veraltete Fassung vorhaelt. */
|
|
var swAktiv = 0;
|
|
if ("serviceWorker" in navigator) {
|
|
try {
|
|
var regs = await navigator.serviceWorker.getRegistrations();
|
|
swAktiv = regs.filter(function (r) {
|
|
return (r.scope || "").indexOf("/webdesign") >= 0;
|
|
}).length;
|
|
} catch (e) { swAktiv = -1; }
|
|
}
|
|
|
|
/* 2. Zwischenspeicher -- und der Vergleich, auf den es ankommt. */
|
|
var gefunden = [];
|
|
if ("caches" in window) {
|
|
try {
|
|
var namen = await caches.keys();
|
|
gefunden = namen.filter(function (n) { return n.indexOf("webdesign") >= 0; });
|
|
} catch (e) { gefunden = []; }
|
|
}
|
|
|
|
var aktuell = !sollFassung || !gefunden.length || gefunden.indexOf(sollFassung) >= 0;
|
|
var veraltet = gefunden.filter(function (n) { return n !== sollFassung; });
|
|
|
|
if (swAktiv < 0) {
|
|
zeile("warn", "Service Worker", "Konnte nicht geprüft werden.");
|
|
} else if (!swAktiv) {
|
|
zeile("gut", "Service Worker", "Keiner aktiv — alles kommt direkt vom Server.");
|
|
} else if (aktuell && !veraltet.length) {
|
|
zeile("gut", "Service Worker",
|
|
"Aktiv und aktuell. Das ist so gewollt — dadurch lässt sich die Seite als App installieren.");
|
|
} else {
|
|
zeile("warn", "Service Worker",
|
|
"Aktiv, hält aber eine veraltete Fassung vor. Der Knopf unten setzt ihn zurück.");
|
|
}
|
|
|
|
if (!("caches" in window)) {
|
|
zeile("gut", "Zwischenspeicher", "Wird von diesem Browser nicht genutzt.");
|
|
} else if (!gefunden.length) {
|
|
zeile("gut", "Zwischenspeicher", "Leer.");
|
|
} else if (aktuell && !veraltet.length) {
|
|
zeile("gut", "Zwischenspeicher",
|
|
"Aktuelle Fassung (" + gefunden.join(", ") + ")" +
|
|
(sollFassung ? " — stimmt mit dem Server überein." : "."));
|
|
} else {
|
|
zeile("warn", "Zwischenspeicher",
|
|
"Veraltet: " + veraltet.join(", ") +
|
|
(sollFassung ? ". Der Server liefert " + sollFassung + "." : "."));
|
|
}
|
|
|
|
/* 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("gut", "Anmeldung Verwaltung",
|
|
"In diesem Tab keiner — das ist normal und KEIN Fehler. Der Schlüssel gilt "
|
|
+ "je Tab, und er soll beim Schliessen der Seite ohnehin verschwinden. "
|
|
+ "Die Verwaltung fragt dann einmal nach dem Zugangscode.");
|
|
} 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>
|