Oberflaeche fuer Auskunft und Loeschung
Sitzt im Kunden-Bereich, zugeklappt. Ein eigener Reiter waere zu prominent fuer etwas, das man vielleicht zweimal im Jahr braucht -- ganz weglassen hiesse, im Ernstfall von Hand durch ein Dutzend Tabellen zu suchen, bei laufender Monatsfrist. DREI SCHRITTE, IN DIESER REIHENFOLGE Auskunft erstellen unschaedlich, jederzeit Loeschvorschau zeigt, was betroffen waere Loeschen nur nach Vorschau, mit doppelter Eingabe Die Auskunft laesst sich als Datei herunterladen, nicht nur ansehen. Man muss sie der Person schicken koennen, und zwar vollstaendig -- abtippen waere der sicherste Weg, etwas zu vergessen. JSON, weil Art. 20 DSGVO ein "gaengiges, maschinenlesbares Format" verlangt. Die Vorschau zeigt beides getrennt: was geloescht wuerde, und was bleiben muss -- mit Grund und mit dem Datum, ab dem es weg darf. Ohne diese Angabe muesste man bei jeder Anfrage neu nachschlagen, welche Frist gilt. Vor dem Loeschen wird die Adresse ein zweites Mal verlangt. Der Unterschied zwischen .de und .com ist ein Buchstabe, die Folge unwiederbringlich. GEPRUEFT Im Handy-Format mit nachgebildeten Antworten: Auskunft zeigt die Bereiche samt Kennzeichnung "aufbewahrungspflichtig", der Herunterladen-Knopf erscheint, die Vorschau trennt richtig, eine abweichende Bestaetigung wird abgelehnt. Kein Ueberlauf, keine Skriptfehler. Die Handy-Suite bleibt bei 25/25, keine Namenskollision. Ein Fehler lag dabei im Test selbst: Playwright prueft die ZULETZT registrierte Route zuerst, und meine allgemeine Auffangregel ueberdeckte die spezifischen. Die Auskunft meldete "nichts gespeichert", obwohl die Oberflaeche richtig arbeitete. Die Endpunkte dazu liegen in server-internal und brauchen noch einen Deploy durch Filipe (/home/dogiintern, Rechte 700). Bis dahin meldet die Oberflaeche einen 404 -- mit dem Hinweis, dass der Server einen Neustart mit dem neuen Stand braucht, statt nur "Fehler" zu sagen. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -863,7 +863,7 @@
|
|||||||
Stand haben. Die Nummer wird zusammen mit CACHE_NAME in sw.js
|
Stand haben. Die Nummer wird zusammen mit CACHE_NAME in sw.js
|
||||||
hochgezaehlt -- beide gehoeren zusammen. */
|
hochgezaehlt -- beide gehoeren zusammen. */
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register("/webdesign/sw.js?v=60", { scope: "/webdesign/" })
|
.register("/webdesign/sw.js?v=61", { scope: "/webdesign/" })
|
||||||
.catch(function () { /* still schlucken: ohne SW funktioniert alles weiter */ });
|
.catch(function () { /* still schlucken: ohne SW funktioniert alles weiter */ });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@
|
|||||||
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
|
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
|
||||||
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
|
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
|
||||||
Stand fest. */
|
Stand fest. */
|
||||||
const CACHE_NAME = "dogfather-webdesign-v60";
|
const CACHE_NAME = "dogfather-webdesign-v61";
|
||||||
|
|
||||||
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
|
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
|
||||||
HTML-Datei in dieser Liste. */
|
HTML-Datei in dieser Liste. */
|
||||||
|
|||||||
@@ -2275,6 +2275,43 @@
|
|||||||
|
|
||||||
<h2 style="font-size:1.15rem">Bestehende Zugänge</h2>
|
<h2 style="font-size:1.15rem">Bestehende Zugänge</h2>
|
||||||
<div id="vw-kunden-liste"><div class="vw-laedt">Wird geladen…</div></div>
|
<div id="vw-kunden-liste"><div class="vw-laedt">Wird geladen…</div></div>
|
||||||
|
|
||||||
|
<!-- Auskunft und Löschung (DSGVO Art. 15/17).
|
||||||
|
|
||||||
|
Steht bewusst hier unten und aufgeklappt-verborgen: Es ist
|
||||||
|
ein seltener Vorgang, aber wenn er kommt, läuft eine Frist
|
||||||
|
von einem Monat. Ein eigener Reiter wäre zu prominent für
|
||||||
|
etwas, das man vielleicht zweimal im Jahr braucht — ganz
|
||||||
|
weglassen hieße, im Ernstfall von Hand durch ein Dutzend
|
||||||
|
Tabellen zu suchen. -->
|
||||||
|
<details class="wd-karte" id="vw-ds" style="margin-top:1.6rem">
|
||||||
|
<summary style="cursor:pointer;font-weight:700;min-height:44px;display:flex;align-items:center">
|
||||||
|
Auskunft und Löschung (DSGVO)
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<p class="wd-klein wd-leise" style="margin-top:.8rem">
|
||||||
|
Für Anfragen nach Art. 15 („Welche Daten haben Sie über mich?“)
|
||||||
|
und Art. 17 („Bitte löschen Sie meine Daten“). Beides ist
|
||||||
|
binnen eines Monats zu beantworten.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="wd-feld" style="margin-top:1rem">
|
||||||
|
<label for="vw-ds-mail">E-Mail-Adresse der Person</label>
|
||||||
|
<input type="email" id="vw-ds-mail" autocomplete="off"
|
||||||
|
placeholder="[email protected]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wd-btn-reihe" style="margin-top:.8rem">
|
||||||
|
<button type="button" class="wd-btn wd-btn--klein" id="vw-ds-auskunft">
|
||||||
|
Auskunft erstellen
|
||||||
|
</button>
|
||||||
|
<button type="button" class="wd-btn wd-btn--zweit wd-btn--klein" id="vw-ds-vorschau">
|
||||||
|
Was würde eine Löschung betreffen?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="vw-ds-ergebnis" style="margin-top:1rem"></div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ PROJEKTE ============ -->
|
<!-- ============ PROJEKTE ============ -->
|
||||||
@@ -2966,6 +3003,162 @@
|
|||||||
return browser + (system ? " auf " + system : "");
|
return browser + (system ? " auf " + system : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =====================================================================
|
||||||
|
AUSKUNFT UND LÖSCHUNG (DSGVO Art. 15/17)
|
||||||
|
|
||||||
|
Drei Schritte, und die Reihenfolge ist Absicht: sehen, Vorschau,
|
||||||
|
erst dann ausführen. Eine Löschung ist endgültig — sie gehört nicht
|
||||||
|
hinter einen Knopf, der sofort zuschlägt.
|
||||||
|
|
||||||
|
Die Auskunft wird als Datei zum Herunterladen angeboten, nicht nur
|
||||||
|
angezeigt. Man muss sie der Person schicken können, und zwar
|
||||||
|
vollständig; abtippen wäre der sicherste Weg, etwas zu vergessen.
|
||||||
|
===================================================================== */
|
||||||
|
function dsMail() {
|
||||||
|
var f = document.getElementById("vw-ds-mail");
|
||||||
|
return f ? f.value.trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function dsMeldung(html, art) {
|
||||||
|
var z = document.getElementById("vw-ds-ergebnis");
|
||||||
|
if (!z) return;
|
||||||
|
var farbe = art === "warn" ? "var(--wd-lila-hell)"
|
||||||
|
: art === "gut" ? "var(--wd-aqua)" : "var(--wd-rand)";
|
||||||
|
z.innerHTML = '<div style="border-left:3px solid ' + farbe +
|
||||||
|
';padding:.8rem 1rem;background:rgb(255 255 255 / .03);border-radius:8px">' + html + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dsAuskunft() {
|
||||||
|
var mail = dsMail();
|
||||||
|
if (!mail) return dsMeldung("Bitte zuerst eine E-Mail-Adresse eintragen.", "warn");
|
||||||
|
dsMeldung("Wird zusammengetragen…");
|
||||||
|
|
||||||
|
var d;
|
||||||
|
try {
|
||||||
|
d = await api("/webdesign/admin/datenschutz/auskunft", { method: "POST", body: { email: mail } });
|
||||||
|
} catch (fehler) {
|
||||||
|
return dsMeldung("Nicht möglich: " + s((fehler && fehler.message) || "unbekannter Fehler") +
|
||||||
|
(fehler && fehler.status === 404
|
||||||
|
? '<br /><span class="wd-klein wd-leise">Der Server kennt diesen Zugang noch nicht — er braucht einen Neustart mit dem neuen Stand.</span>'
|
||||||
|
: ""), "warn");
|
||||||
|
}
|
||||||
|
if (!d || !d.ok) return dsMeldung("Nicht möglich: " + s((d && d.error) || "unbekannt"), "warn");
|
||||||
|
|
||||||
|
if (!d.datensaetze) {
|
||||||
|
return dsMeldung("Zu <strong>" + s(mail) + "</strong> ist nichts gespeichert.", "gut");
|
||||||
|
}
|
||||||
|
|
||||||
|
var liste = (d.bereiche || []).map(function (b) {
|
||||||
|
return "<li>" + s(String(b.anzahl)) + "× " + s(b.was) +
|
||||||
|
(b.aufbewahren ? ' <span class="wd-klein wd-leise">(aufbewahrungspflichtig)</span>' : "") +
|
||||||
|
"</li>";
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
dsMeldung(
|
||||||
|
"<strong>" + s(String(d.datensaetze)) + " Datensätze</strong> zu " + s(mail) +
|
||||||
|
'<ul class="wd-liste" style="margin:.6rem 0">' + liste + "</ul>" +
|
||||||
|
'<div class="wd-btn-reihe">' +
|
||||||
|
'<button type="button" class="wd-btn wd-btn--zweit wd-btn--klein" id="vw-ds-laden">' +
|
||||||
|
"Als Datei herunterladen</button></div>", "gut");
|
||||||
|
|
||||||
|
var knopf = document.getElementById("vw-ds-laden");
|
||||||
|
if (knopf) {
|
||||||
|
knopf.addEventListener("click", function () {
|
||||||
|
/* Die vollständige Auskunft, wie sie herausgegeben wird. JSON,
|
||||||
|
weil Art. 20 ein "gängiges, maschinenlesbares Format"
|
||||||
|
verlangt — und weil nichts verloren geht. */
|
||||||
|
var blob = new Blob([JSON.stringify(d, null, 2)], { type: "application/json" });
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
var a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = "auskunft-" + mail.replace(/[^a-z0-9]/gi, "-") + "-" +
|
||||||
|
new Date().toISOString().slice(0, 10) + ".json";
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dsVorschau() {
|
||||||
|
var mail = dsMail();
|
||||||
|
if (!mail) return dsMeldung("Bitte zuerst eine E-Mail-Adresse eintragen.", "warn");
|
||||||
|
dsMeldung("Wird geprüft…");
|
||||||
|
|
||||||
|
var d;
|
||||||
|
try {
|
||||||
|
d = await api("/webdesign/admin/datenschutz/vorschau", { method: "POST", body: { email: mail } });
|
||||||
|
} catch (fehler) {
|
||||||
|
return dsMeldung("Nicht möglich: " + s((fehler && fehler.message) || "unbekannt"), "warn");
|
||||||
|
}
|
||||||
|
if (!d || !d.ok) return dsMeldung("Nicht möglich: " + s((d && d.error) || "unbekannt"), "warn");
|
||||||
|
|
||||||
|
var weg = (d.loeschbar || []).map(function (x) {
|
||||||
|
return "<li>" + s(String(x.anzahl)) + "× " + s(x.was) + "</li>";
|
||||||
|
}).join("") || '<li class="wd-leise">nichts</li>';
|
||||||
|
|
||||||
|
/* Der wichtigere Teil: was NICHT gelöscht werden darf, mit Grund
|
||||||
|
und Datum. Ohne diese Angabe müsste man bei jeder Anfrage neu
|
||||||
|
nachschlagen, welche Frist gilt. */
|
||||||
|
var bleibt = (d.bleibt || []).map(function (x) {
|
||||||
|
return "<li>" + s(String(x.anzahl)) + "× " + s(x.was) +
|
||||||
|
'<br /><span class="wd-klein wd-leise">' + s(x.grund) +
|
||||||
|
" · frei ab " + s(x.freiAb) + "</span></li>";
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
dsMeldung(
|
||||||
|
"<strong>Würde gelöscht</strong>" +
|
||||||
|
'<ul class="wd-liste" style="margin:.4rem 0 .8rem">' + weg + "</ul>" +
|
||||||
|
(bleibt
|
||||||
|
? "<strong>Bleibt (gesetzliche Aufbewahrung)</strong>" +
|
||||||
|
'<ul class="wd-liste" style="margin:.4rem 0 .8rem">' + bleibt + "</ul>"
|
||||||
|
: "") +
|
||||||
|
'<p class="wd-klein">' + s(d.hinweis || "") + "</p>" +
|
||||||
|
((d.loeschbar || []).length
|
||||||
|
? '<div class="wd-feld" style="margin-top:.8rem">' +
|
||||||
|
'<label for="vw-ds-best">Zum Löschen die Adresse noch einmal eintippen</label>' +
|
||||||
|
'<input type="email" id="vw-ds-best" autocomplete="off" placeholder="' + s(mail) + '" />' +
|
||||||
|
"</div>" +
|
||||||
|
'<div class="wd-btn-reihe" style="margin-top:.6rem">' +
|
||||||
|
'<button type="button" class="wd-btn wd-btn--klein" id="vw-ds-loeschen">' +
|
||||||
|
"Jetzt löschen</button></div>"
|
||||||
|
: ""), "warn");
|
||||||
|
|
||||||
|
var lk = document.getElementById("vw-ds-loeschen");
|
||||||
|
if (lk) lk.addEventListener("click", dsLoeschen);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dsLoeschen() {
|
||||||
|
var mail = dsMail();
|
||||||
|
var best = document.getElementById("vw-ds-best");
|
||||||
|
var bestaetigung = best ? best.value.trim() : "";
|
||||||
|
|
||||||
|
if (mail.toLowerCase() !== bestaetigung.toLowerCase()) {
|
||||||
|
return dsMeldung("Die beiden Adressen stimmen nicht überein. " +
|
||||||
|
"Zum Schutz vor einem Vertipper wird sie ein zweites Mal verlangt — " +
|
||||||
|
"der Unterschied zwischen .de und .com ist ein Buchstabe, die Folge unwiederbringlich.", "warn");
|
||||||
|
}
|
||||||
|
|
||||||
|
dsMeldung("Wird gelöscht…");
|
||||||
|
var d;
|
||||||
|
try {
|
||||||
|
d = await api("/webdesign/admin/datenschutz/loeschen",
|
||||||
|
{ method: "POST", body: { email: mail, bestaetigung: bestaetigung } });
|
||||||
|
} catch (fehler) {
|
||||||
|
return dsMeldung("Nicht möglich: " + s((fehler && fehler.message) || "unbekannt"), "warn");
|
||||||
|
}
|
||||||
|
if (!d || !d.ok) return dsMeldung("Nicht möglich: " + s((d && d.error) || "unbekannt"), "warn");
|
||||||
|
|
||||||
|
dsMeldung("<strong>" + s(String(d.geloescht)) + " Datensätze gelöscht.</strong>" +
|
||||||
|
'<p class="wd-klein" style="margin-top:.5rem">' + s(d.hinweis || "") + "</p>", "gut");
|
||||||
|
}
|
||||||
|
|
||||||
|
(function dsVerdrahten() {
|
||||||
|
var a = document.getElementById("vw-ds-auskunft");
|
||||||
|
if (a) a.addEventListener("click", dsAuskunft);
|
||||||
|
var v = document.getElementById("vw-ds-vorschau");
|
||||||
|
if (v) v.addEventListener("click", dsVorschau);
|
||||||
|
})();
|
||||||
|
|
||||||
async function ladeUebersicht() {
|
async function ladeUebersicht() {
|
||||||
var laedt = document.getElementById("vw-ub-laedt");
|
var laedt = document.getElementById("vw-ub-laedt");
|
||||||
var inhalt = document.getElementById("vw-ub-inhalt");
|
var inhalt = document.getElementById("vw-ub-inhalt");
|
||||||
|
|||||||
Reference in New Issue
Block a user