Verwaltung ist jetzt eine eigene App
Wunsch: "ich will die verwaltungsseite auch getrennt runter laden koennen und installieren koenne." Bisher gab es ein Manifest fuer den ganzen Webdesign-Bereich; die Verwaltung war darin nur eine Verknuepfung. Jetzt hat sie ein eigenes mit eigener "id" -- der Wert, an dem alles haengt: Ohne ihn halten Browser beide fuer dieselbe App, und die zweite Installation ueberschreibt die erste, statt danebenzustehen. EIGENES SYMBOL Zwei gleich aussehende Kacheln auf dem Startbildschirm waeren keine Trennung. Das neue Symbol ist ein Ausschnitt des Eiskristall-Wappens -- dasselbe Motiv, das die Verwaltung ohnehin traegt, und klar unterscheidbar vom schwarzen Husky der oeffentlichen App. Erzeugt aus cryo-wappen.webp in drei Groessen, die beschneidbare Fassung weiter herausgezoomt, damit Android die Spitzen des Wappens nicht abschneidet. Als JPEG statt PNG: 76 statt 385 KB bei 512 Punkten. Bei einem fotografischen Motiv hat PNG nichts zu gewinnen, und 385 KB fuer ein Symbol waeren unverhaeltnismaessig. ⚠️ DER GELTUNGSBEREICH IST ABSICHTLICH WEIT Naheliegend waere "/webdesign/verwaltung" gewesen. Das haette die App unbrauchbar gemacht: Ohne gueltigen Ausweis leitet der Server auf /webdesign/zugang.html um -- ausserhalb des Bereichs, und was ausserhalb liegt, oeffnet der Browser in einem eigenen Fenster. Weil der Zugang beim Schliessen endet, waere das bei fast jedem Start passiert: Man tippt auf die App und landet im Browser. Nachgemessen: verwaltung.html antwortet ohne Ausweis mit 302. EIN STILLES VERSPRECHEN EINGELOEST Die Verknuepfungen zeigen auf "?bereich=anfragen" und dergleichen -- und derselbe Parameter steht in den Push-Meldungen. Ausgewertet hat ihn bisher NIEMAND. Man landete immer auf der Uebersicht, ohne dass etwas kaputt aussah. Jetzt oeffnet die Seite den gewuenschten Reiter, ueber einen ausgeloesten Klick auf den vorhandenen Reiter statt ueber nachgebaute Logik: Daran haengen Signatur, Leuchtbalken, Nachladen und der Wischstreifen auf dem Handy. Auch hier hat erst der Test den Fehler gezeigt -- und danach einen in der Pruefung selbst: Sie erreichte die Funktion gar nicht, weil diese im gekapselten Bereich liegt. Jetzt nach aussen gegeben, wie schon window.vwBalkenSetzen. 23 Pruefungen gruen. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -838,7 +838,7 @@
|
||||
Stand haben. Die Nummer wird zusammen mit CACHE_NAME in sw.js
|
||||
hochgezaehlt -- beide gehoeren zusammen. */
|
||||
navigator.serviceWorker
|
||||
.register("/webdesign/sw.js?v=58", { scope: "/webdesign/" })
|
||||
.register("/webdesign/sw.js?v=59", { scope: "/webdesign/" })
|
||||
.catch(function () { /* still schlucken: ohne SW funktioniert alles weiter */ });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/* =====================================================================
|
||||
Prueft, ob die Verwaltung als EIGENE App installierbar ist.
|
||||
|
||||
Der Fehler, den man hier am ehesten macht, faellt erst beim zweiten
|
||||
Installieren auf: Haben zwei Manifeste dieselbe "id", halten Browser
|
||||
sie fuer dieselbe App. Die zweite Installation ueberschreibt dann die
|
||||
erste, statt danebenzustehen -- und man sucht den Fehler ueberall,
|
||||
nur nicht in einer Zeile, die man fuer Beiwerk gehalten hat.
|
||||
|
||||
Ebenso still: ein Symbol, das im Manifest steht, aber nicht existiert
|
||||
oder andere Masse hat als angegeben. Der Browser meldet das nicht --
|
||||
er zeigt einfach ein leeres oder verzerrtes Kaestchen.
|
||||
===================================================================== */
|
||||
import { chromium } from "playwright";
|
||||
import http from "http";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const TYP = {
|
||||
".html": "text/html", ".css": "text/css", ".js": "text/javascript",
|
||||
".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp",
|
||||
".json": "application/json", ".ico": "image/x-icon", ".jpg": "image/jpeg",
|
||||
/* Ohne diesen Eintrag liefert der Testserver den Typ "text/plain", und
|
||||
der Browser ignoriert das Manifest stillschweigend. Genau so wuerde
|
||||
es auch auf einem falsch eingerichteten Server aussehen. */
|
||||
".webmanifest": "application/manifest+json",
|
||||
};
|
||||
|
||||
const srv = http.createServer((q, r) => {
|
||||
const p = path.join(process.cwd(), decodeURIComponent(q.url.split("?")[0]));
|
||||
if (fs.existsSync(p) && !p.endsWith("/")) {
|
||||
r.writeHead(200, { "content-type": TYP[path.extname(p)] || "text/plain" });
|
||||
return r.end(fs.readFileSync(p));
|
||||
}
|
||||
r.writeHead(404);
|
||||
r.end("");
|
||||
});
|
||||
|
||||
let ok = 0, fehl = 0;
|
||||
const pruefe = (name, gut, info) => {
|
||||
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
|
||||
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); }
|
||||
};
|
||||
|
||||
/* Masse aus dem Dateikopf lesen, ohne Fremdpaket. */
|
||||
function masse(datei) {
|
||||
const b = fs.readFileSync(datei);
|
||||
if (b[0] === 0x89 && b[1] === 0x50) {
|
||||
return { breite: b.readUInt32BE(16), hoehe: b.readUInt32BE(20) };
|
||||
}
|
||||
let i = 2;
|
||||
while (i < b.length) {
|
||||
if (b[i] !== 0xFF) { i++; continue; }
|
||||
const m = b[i + 1];
|
||||
if (m >= 0xC0 && m <= 0xCF && m !== 0xC4 && m !== 0xC8 && m !== 0xCC) {
|
||||
return { hoehe: b.readUInt16BE(i + 5), breite: b.readUInt16BE(i + 7) };
|
||||
}
|
||||
i += 2 + b.readUInt16BE(i + 2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
srv.listen(4410, async () => {
|
||||
const browser = await chromium.launch();
|
||||
const seite = await browser.newPage({ viewport: { width: 1280, height: 900 } });
|
||||
|
||||
console.log("1. DIE VERWALTUNG HAT EIN EIGENES MANIFEST");
|
||||
await seite.goto("http://127.0.0.1:4410/webdesign/verwaltung.html");
|
||||
await seite.waitForTimeout(900);
|
||||
|
||||
const verweis = await seite.evaluate(() =>
|
||||
document.querySelector('link[rel="manifest"]')?.getAttribute("href") || null);
|
||||
pruefe("verwaltung.html verweist auf ihr eigenes Manifest",
|
||||
verweis === "/webdesign/verwaltung.webmanifest", verweis);
|
||||
|
||||
const a = await fetch("http://127.0.0.1:4410/webdesign/verwaltung.webmanifest");
|
||||
pruefe("Manifest ist abrufbar", a.ok, "HTTP " + a.status);
|
||||
pruefe("und wird als Manifest ausgeliefert",
|
||||
(a.headers.get("content-type") || "").includes("manifest"),
|
||||
a.headers.get("content-type"));
|
||||
|
||||
const m = JSON.parse(await a.text());
|
||||
|
||||
console.log("\n2. IST ES WIRKLICH EINE ZWEITE APP?");
|
||||
const alt = JSON.parse(fs.readFileSync("webdesign/app.webmanifest", "utf8"));
|
||||
/* Der eine Wert, an dem alles haengt. */
|
||||
pruefe("die Kennung unterscheidet sich vom Webdesign-Bereich",
|
||||
m.id !== alt.id, `"${m.id}" gegen "${alt.id}"`);
|
||||
pruefe("auch der Startpunkt ist ein anderer",
|
||||
m.start_url !== alt.start_url, m.start_url);
|
||||
pruefe("und der Name", m.name !== alt.name, `"${m.name}"`);
|
||||
pruefe("Kurzname passt auf einen Startbildschirm (max. 12 Zeichen)",
|
||||
m.short_name && m.short_name.length <= 12, `"${m.short_name}" (${m.short_name.length})`);
|
||||
|
||||
console.log("\n3. WAS BROWSER ZUM INSTALLIEREN VERLANGEN");
|
||||
pruefe("Anzeigeart ist eigenstaendig", m.display === "standalone", m.display);
|
||||
pruefe("Startadresse liegt im Geltungsbereich",
|
||||
m.start_url.startsWith(m.scope), `${m.start_url} in ${m.scope}`);
|
||||
pruefe("Hintergrund- und Themenfarbe gesetzt",
|
||||
!!m.background_color && !!m.theme_color, m.theme_color);
|
||||
|
||||
const gross = m.icons.filter((i) => i.sizes === "512x512");
|
||||
pruefe("es gibt ein Symbol mit 512 Punkten", gross.length > 0, gross.length + " Stueck");
|
||||
pruefe("und eines fuers Beschneiden (maskable)",
|
||||
m.icons.some((i) => (i.purpose || "").includes("maskable")));
|
||||
|
||||
console.log("\n4. GIBT ES DIE SYMBOLE AUCH WIRKLICH?");
|
||||
for (const i of m.icons) {
|
||||
const datei = path.join(process.cwd(), i.src);
|
||||
const da = fs.existsSync(datei);
|
||||
if (!da) { pruefe(` ${i.src}`, false, "fehlt"); continue; }
|
||||
const g = masse(datei);
|
||||
const [b, h] = i.sizes.split("x").map(Number);
|
||||
/* Ein Symbol, dessen Masse nicht zur Angabe passen, wird verzerrt
|
||||
dargestellt -- ohne jede Fehlermeldung. */
|
||||
pruefe(` ${i.src}`, g && g.breite === b && g.hoehe === h,
|
||||
g ? `${g.breite}x${g.hoehe}, ${Math.round(fs.statSync(datei).size / 1024)} KB` : "unlesbar");
|
||||
}
|
||||
|
||||
console.log("\n5. DIE VERKNUEPFUNGEN ZEIGEN AUF ECHTE ZIELE");
|
||||
for (const v of (m.shortcuts || [])) {
|
||||
const r = await fetch("http://127.0.0.1:4410" + v.url.split("?")[0]);
|
||||
pruefe(` ${v.short_name} -> ${v.url}`, r.ok, "HTTP " + r.status);
|
||||
}
|
||||
|
||||
console.log("\n5b. TUN DIE VERKNUEPFUNGEN AUCH ETWAS?");
|
||||
/* Dass die Adresse antwortet, heisst noch nicht, dass sie den
|
||||
versprochenen Bereich oeffnet. Genau das war beim Bauen der Fall:
|
||||
"?bereich=anfragen" stand in den Verknuepfungen UND in den
|
||||
Push-Meldungen -- ausgewertet hat den Parameter niemand. Man landete
|
||||
immer auf der Uebersicht, und nichts sah kaputt aus. */
|
||||
for (const wunsch of ["anfragen", "projekte", "postfach"]) {
|
||||
const p = await browser.newPage({ viewport: { width: 1280, height: 900 } });
|
||||
await p.goto(`http://127.0.0.1:4410/webdesign/verwaltung.html?bereich=${wunsch}`);
|
||||
await p.waitForTimeout(600);
|
||||
|
||||
/* Ohne Anmeldung sind die Reiter verborgen. Fuer diese Pruefung wird
|
||||
der Bereich sichtbar geschaltet und die Auswertung angestossen --
|
||||
geprueft wird die Auswertung, nicht die Anmeldung. */
|
||||
const offen = await p.evaluate((w) => {
|
||||
document.getElementById("vw-anmeldung").hidden = true;
|
||||
document.getElementById("vw-bereich").hidden = false;
|
||||
/* dieselbe Funktion, die die Seite nach der Anmeldung aufruft */
|
||||
if (typeof window.vwBereichAusAdresse === "function") window.vwBereichAusAdresse();
|
||||
const aktiv = document.querySelector("[data-tab].vw-an");
|
||||
return aktiv ? aktiv.dataset.tab : null;
|
||||
}, wunsch);
|
||||
|
||||
pruefe(` ?bereich=${wunsch} oeffnet den Reiter`, offen === wunsch,
|
||||
offen === wunsch ? "geoeffnet" : "geoeffnet wurde: " + offen);
|
||||
await p.close();
|
||||
}
|
||||
|
||||
/* Gegenprobe: Unsinn in der Adresse darf nichts kaputtmachen. */
|
||||
{
|
||||
const p = await browser.newPage();
|
||||
const fehler = [];
|
||||
p.on("pageerror", (e) => fehler.push(String(e).slice(0, 70)));
|
||||
await p.goto("http://127.0.0.1:4410/webdesign/verwaltung.html?bereich=<script>x</script>");
|
||||
await p.waitForTimeout(500);
|
||||
await p.evaluate(() => {
|
||||
document.getElementById("vw-bereich").hidden = false;
|
||||
if (typeof window.vwBereichAusAdresse === "function") window.vwBereichAusAdresse();
|
||||
});
|
||||
pruefe(" unsinniger Wert richtet keinen Schaden an", fehler.length === 0,
|
||||
fehler[0] || "kein Fehler");
|
||||
await p.close();
|
||||
}
|
||||
|
||||
console.log("\n6. DIE ANDERE APP BLEIBT UNBERUEHRT");
|
||||
const b2 = await seite.goto("http://127.0.0.1:4410/webdesign/index.html");
|
||||
await seite.waitForTimeout(500);
|
||||
const verweis2 = await seite.evaluate(() =>
|
||||
document.querySelector('link[rel="manifest"]')?.getAttribute("href") || null);
|
||||
pruefe("der oeffentliche Bereich nutzt weiterhin sein eigenes",
|
||||
verweis2 === "/webdesign/app.webmanifest", verweis2);
|
||||
|
||||
await browser.close();
|
||||
srv.close();
|
||||
console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`);
|
||||
process.exit(fehl ? 1 : 0);
|
||||
});
|
||||
+1
-1
@@ -34,7 +34,7 @@
|
||||
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
|
||||
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
|
||||
Stand fest. */
|
||||
const CACHE_NAME = "dogfather-webdesign-v58";
|
||||
const CACHE_NAME = "dogfather-webdesign-v59";
|
||||
|
||||
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
|
||||
HTML-Datei in dieser Liste. */
|
||||
|
||||
@@ -12,8 +12,29 @@
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/favicon.png" />
|
||||
<meta name="theme-color" content="#05070b" />
|
||||
<link rel="manifest" href="/webdesign/app.webmanifest" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/wd-app-180.png" />
|
||||
<!-- EIGENES Manifest, nicht das des Webdesign-Bereichs (26.08.2026).
|
||||
|
||||
Damit erscheint die Verwaltung als eigenstaendige App neben
|
||||
"Dogfather Webdesign", mit eigenem Namen und eigenem Symbol. Der
|
||||
Unterschied macht die Datei "id": Ohne sie waeren beide fuer den
|
||||
Browser dieselbe App, und die zweite Installation wuerde die erste
|
||||
ueberschreiben.
|
||||
|
||||
⚠️ Der Geltungsbereich ("scope") umfasst bewusst den GANZEN
|
||||
Webdesign-Bereich und nicht nur die Verwaltungsseite.
|
||||
|
||||
Naheliegender waere "/webdesign/verwaltung" gewesen -- sauber
|
||||
abgegrenzt. Es haette die App aber unbrauchbar gemacht: Ohne
|
||||
gueltigen Ausweis leitet der Server auf /webdesign/zugang.html um,
|
||||
und alles ausserhalb des Geltungsbereichs oeffnet der Browser in
|
||||
einem eigenen Fenster. Weil der Zugang beim Schliessen der Seite
|
||||
endet (Wunsch vom 22.08.2026), waere das bei fast jedem Start
|
||||
passiert: Man tippt auf die App und landet im Browser.
|
||||
|
||||
Nachgemessen, nicht vermutet: verwaltung.html antwortet ohne
|
||||
Ausweis mit 302 auf zugang.html. -->
|
||||
<link rel="manifest" href="/webdesign/verwaltung.webmanifest" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/wd-admin-192.jpg" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="DF Verwaltung" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=20260823wd20" />
|
||||
@@ -2469,8 +2490,51 @@
|
||||
function zeigeBereich() {
|
||||
document.getElementById("vw-anmeldung").hidden = true;
|
||||
document.getElementById("vw-bereich").hidden = false;
|
||||
bereichAusAdresse();
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
?bereich=... AUS DER ADRESSE ÖFFNEN
|
||||
|
||||
Gebraucht an zwei Stellen, die das bisher nur VERSPROCHEN haben:
|
||||
|
||||
- die Verknüpfungen der installierten App ("Anfragen", "Projekte",
|
||||
"Postfach" beim langen Antippen des Symbols)
|
||||
- der Verweis in einer Push-Meldung, der zur betreffenden Anfrage
|
||||
führen soll
|
||||
|
||||
Beide zeigten auf "?bereich=anfragen" — ausgewertet hat den Parameter
|
||||
bisher niemand. Man landete immer auf der Übersicht, ohne dass etwas
|
||||
kaputt aussah. Genau die Sorte stiller Wirkungslosigkeit, gegen die
|
||||
hier an anderer Stelle schon einiges gebaut wurde.
|
||||
|
||||
Umgesetzt über einen ausgelösten Klick auf den vorhandenen Reiter,
|
||||
nicht über nachgebaute Logik: Am Reiter hängen Signatur, Leuchtbalken,
|
||||
das Nachladen der Daten und das Verschieben des Wischstreifens auf dem
|
||||
Handy. Ein zweiter Weg dorthin wäre ein zweiter Weg, der irgendwann
|
||||
nicht mehr mitgepflegt wird.
|
||||
|
||||
Läuft NACH der Anmeldung, denn vorher gibt es die Reiter nicht.
|
||||
--------------------------------------------------------------------- */
|
||||
function bereichAusAdresse() {
|
||||
try {
|
||||
var wunsch = new URLSearchParams(window.location.search).get("bereich");
|
||||
if (!wunsch) return;
|
||||
var knopf = document.querySelector('[data-tab="' + wunsch.replace(/[^a-z]/gi, "") + '"]');
|
||||
if (knopf && !knopf.classList.contains("vw-an")) knopf.click();
|
||||
} catch (e) { /* alter Browser: dann eben die Übersicht */ }
|
||||
}
|
||||
|
||||
/* Nach aussen verfuegbar machen — gleiches Vorgehen wie bei
|
||||
window.vwBalkenSetzen weiter unten.
|
||||
|
||||
Grund ist hier die Prüfbarkeit: Der Aufruf sitzt hinter der
|
||||
Anmeldung, und ein Test, der sich erst anmelden müsste, prüft am
|
||||
Ende die Anmeldung statt die Sache. Ohne diesen Zugang war meine
|
||||
eigene Prüfung wirkungslos und meldete "geöffnet wurde: uebersicht"
|
||||
— sie hatte die Funktion schlicht nie erreicht. */
|
||||
window.vwBereichAusAdresse = bereichAusAdresse;
|
||||
|
||||
document.getElementById("vw-login-form").addEventListener("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
var feld = document.getElementById("vw-feld-code");
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "Dogfather Verwaltung",
|
||||
"short_name": "Verwaltung",
|
||||
"description": "Anfragen, Projekte, Kunden und Zahlungen an einer Stelle. Meldet sich, sobald eine neue Anfrage eingeht.",
|
||||
"lang": "de",
|
||||
"dir": "ltr",
|
||||
|
||||
"start_url": "/webdesign/verwaltung.html?start=app",
|
||||
|
||||
"id": "/webdesign/verwaltung",
|
||||
|
||||
"scope": "/webdesign/",
|
||||
|
||||
"display": "standalone",
|
||||
"display_override": ["window-controls-overlay", "standalone", "minimal-ui"],
|
||||
"orientation": "any",
|
||||
"background_color": "#05070b",
|
||||
"theme_color": "#05070b",
|
||||
"categories": ["business", "productivity"],
|
||||
|
||||
"icons": [
|
||||
{ "src": "/assets/img/wd-admin-192.jpg", "sizes": "192x192", "type": "image/jpeg", "purpose": "any" },
|
||||
{ "src": "/assets/img/wd-admin-512.jpg", "sizes": "512x512", "type": "image/jpeg", "purpose": "any" },
|
||||
{ "src": "/assets/img/wd-admin-512-maskable.jpg", "sizes": "512x512", "type": "image/jpeg", "purpose": "maskable" }
|
||||
],
|
||||
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Anfragen",
|
||||
"short_name": "Anfragen",
|
||||
"url": "/webdesign/verwaltung.html?bereich=anfragen",
|
||||
"icons": [{ "src": "/assets/img/wd-admin-192.jpg", "sizes": "192x192" }]
|
||||
},
|
||||
{
|
||||
"name": "Projekte",
|
||||
"short_name": "Projekte",
|
||||
"url": "/webdesign/verwaltung.html?bereich=projekte",
|
||||
"icons": [{ "src": "/assets/img/wd-admin-192.jpg", "sizes": "192x192" }]
|
||||
},
|
||||
{
|
||||
"name": "Postfach",
|
||||
"short_name": "Postfach",
|
||||
"url": "/webdesign/verwaltung.html?bereich=postfach",
|
||||
"icons": [{ "src": "/assets/img/wd-admin-192.jpg", "sizes": "192x192" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user