From e910017e5159462bc776e1eb37d87f3cb8da0b02 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sat, 1 Aug 2026 11:04:01 +0200 Subject: [PATCH] =?UTF-8?q?Interne=20Bewerbungs-R=C3=BCckseiten=20(Postfac?= =?UTF-8?q?h,=20Zug=C3=A4nge,=20Login)=20komplett=20entfernt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auf Wunsch: die öffentlichen Bewerbungsseiten (bewerben*.html) bleiben unverändert bestehen, aber die internen "Rückseiten", wo die Bewerbungen verwaltet wurden, werden nicht mehr gebraucht und sind komplett raus: - postfach.html (Bewerbungs-Postfach) und zugaenge.html (Zugänge & Rollen) gelöscht - zugehörige tote Skripte gelöscht: postfach.js, zugaenge.js, admin-auth.js - "Login"-Knopf in der Navigation entfernt - "Intern"-Block im Footer (Bewerbungs-Postfach / Zugänge & Rollen) entfernt - KEIN_NAHTLOSER_WECHSEL-Ausnahmeliste geleert (referenzierte nur die jetzt gelöschten Seiten) - .nav-login CSS-Regeln entfernt (totes CSS) - robots.txt: Disallow-Zeile für /postfach.html entfernt Der Cloudflare-Worker (Backend/D1) und die Bewerbungsformulare selbst bleiben unangetastet — Bewerbungen können weiterhin eingereicht werden. --- assets/css/main.css | 22 -- assets/js/admin-auth.js | 81 ------ assets/js/main.js | 20 +- assets/js/postfach.js | 266 -------------------- assets/js/zugaenge.js | 543 ---------------------------------------- postfach.html | 113 --------- robots.txt | 1 - zugaenge.html | 228 ----------------- 8 files changed, 5 insertions(+), 1269 deletions(-) delete mode 100644 assets/js/admin-auth.js delete mode 100644 assets/js/postfach.js delete mode 100644 assets/js/zugaenge.js delete mode 100644 postfach.html delete mode 100644 zugaenge.html diff --git a/assets/css/main.css b/assets/css/main.css index c199cab..9394c5d 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -260,27 +260,6 @@ header.site-header { .nav-cta::before, .nav-cta::after { animation: none; } } -/* Interner Login — dezent, aber auf jeder Seite oben rechts auffindbar */ -.nav-login { - display: inline-flex; - align-items: center; - gap: .3rem; - padding: .5rem .9rem; - border-radius: 999px; - border: 1px solid var(--border); - color: var(--text-muted); - font-weight: 700; - font-size: .82rem; - white-space: nowrap; - transition: color .2s ease, border-color .2s ease, background .2s ease; -} -.nav-login:hover, -.nav-login:focus-visible { - color: var(--accent); - border-color: var(--accent); - background: color-mix(in srgb, var(--accent) 12%, transparent); -} - .nav-toggle { display: none; background: none; @@ -311,7 +290,6 @@ header.site-header { .nav-dropdown { position: static; transform: none; box-shadow: none; width: 100%; } .nav-group.open .nav-dropdown { display: flex; } .nav-cta { align-self: flex-start; margin: 1rem 0 .6rem; } - .nav-login { align-self: flex-start; } } /* ---------- Buttons ---------- */ diff --git a/assets/js/admin-auth.js b/assets/js/admin-auth.js deleted file mode 100644 index e6b361f..0000000 --- a/assets/js/admin-auth.js +++ /dev/null @@ -1,81 +0,0 @@ -/* ===================================================================== - admin-auth.js — gemeinsame Session-Logik für postfach.html und - zugaenge.html. Session (Token + Rolle/Rechte) liegt in sessionStorage - (pro Tab, verschwindet beim Schließen) — der Server erzwingt zusätzlich - Inaktivitäts-Timeout (30 Min) und eine absolute Höchstdauer (12 Std). - ===================================================================== */ - -const SESSION_KEY = "dogfather_admin_session"; - -function getSession() { - try { - return JSON.parse(sessionStorage.getItem(SESSION_KEY) || "null"); - } catch { - return null; - } -} - -function setSession(data) { - sessionStorage.setItem(SESSION_KEY, JSON.stringify(data)); -} - -function clearSession() { - sessionStorage.removeItem(SESSION_KEY); -} - -/** Prüft, ob die aktuelle Session (Owner oder Rolle+Overrides) ein Recht hat. */ -function sessionHasPermission(session, key) { - if (!session) return false; - if (session.isOwner) return true; - const overrides = session.overrides || {}; - if (Object.prototype.hasOwnProperty.call(overrides, key)) return !!overrides[key]; - return Array.isArray(session.permissions) && session.permissions.includes(key); -} - -async function apiCall(apiBaseUrl, path, body, { auth = true } = {}) { - const session = getSession(); - const headers = { "Content-Type": "application/json" }; - if (auth && session?.token) headers["Authorization"] = `Bearer ${session.token}`; - - const res = await fetch(`${apiBaseUrl}${path}`, { - method: "POST", - headers, - body: JSON.stringify(body || {}), - }); - let data; - try { - data = await res.json(); - } catch { - data = { ok: false, error: "Ungültige Antwort vom Server." }; - } - if (res.status === 401 && auth) { - clearSession(); // Session ist abgelaufen/ungültig -> zurück zum Login zwingen - } - return { httpOk: res.ok, ...data }; -} - -async function doLogin(apiBaseUrl, code) { - const res = await apiCall(apiBaseUrl, "/auth/login", { code }, { auth: false }); - if (res.ok) { - setSession({ - token: res.token, - isOwner: res.isOwner, - name: res.name, - username: res.username, - roleName: res.roleName, - permissions: res.permissions || [], - overrides: res.overrides || {}, - }); - } - return res; -} - -async function doLogout(apiBaseUrl) { - await apiCall(apiBaseUrl, "/auth/logout", {}); - clearSession(); -} - -// Für Einbindung ohne Modul-Bundler global verfügbar machen. -if (typeof window !== "undefined") { - window.AdminAuth = { getSession, setSession, clearSession, sessionHasPermission, apiCall, doLogin, doLogout }; -} diff --git a/assets/js/main.js b/assets/js/main.js index d72a22d..c585847 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -95,11 +95,6 @@ `; @@ -175,16 +170,11 @@
  • Bewerben
  • Kontakt
  • -

    Intern 🔒

    - `; } @@ -286,14 +276,14 @@ Inhaltsbereich
    aus. Kopfzeile, Fußzeile und der Musik-Knopf bleiben stehen — die Musik läuft einfach weiter. - Wichtig für die Sicherheit: Die internen Seiten (Postfach, Zugänge) - sind bewusst AUSGENOMMEN und laden weiterhin ganz normal. An Login, - Sitzung und Rechten ändert sich dadurch nichts. + Die Liste unten kann bei Bedarf wieder Seiten aufnehmen, die bewusst + NICHT nahtlos geladen werden sollen (z.B. künftige interne Bereiche + mit eigenem Login) — aktuell gibt es keine solche Seite mehr. Absicherung: Geht beim Holen irgendetwas schief, wird ganz normal zur Zielseite navigiert. Schlimmstenfalls ist es also wie vorher. =================================================================== */ - const KEIN_NAHTLOSER_WECHSEL = ["postfach.html", "zugaenge.html"]; + const KEIN_NAHTLOSER_WECHSEL = []; /* Seiteneigene Skripte melden sich fast alle über "DOMContentLoaded" an. Nach dem ersten Laden ist dieses Ereignis längst vorbei — ein später diff --git a/assets/js/postfach.js b/assets/js/postfach.js deleted file mode 100644 index 46b7c53..0000000 --- a/assets/js/postfach.js +++ /dev/null @@ -1,266 +0,0 @@ -/* ===================================================================== - postfach.js — Logik für postfach.html - ===================================================================== */ - -const API_BASE_URL = "https://dogfather-universe-postfach.dogfather1608.workers.dev"; - -const { getSession, sessionHasPermission, apiCall, doLogin, doLogout } = window.AdminAuth; - -const STATUS_OPTIONS = ["neu", "in_bearbeitung", "beantwortet"]; - -function $(id) { return document.getElementById(id); } - -function showApp(session) { - $("gate-container").style.display = "none"; - $("session-bar").style.display = "block"; - $("inbox-section").style.display = "block"; - $("role-badge").textContent = session.isOwner ? "Eingeloggt als: Dogi (Owner)" : `Eingeloggt als: ${session.name} (${session.roleName})`; - - const canSeeTeam = session.isOwner || sessionHasPermission(session, "TEAM_VIEW"); - $("team-link").style.display = canSeeTeam ? "inline-flex" : "none"; - - if (session.isOwner) { - $("live-toggle-section").style.display = "block"; - loadLiveStatus(); - } - - loadInbox(); -} - -function showGate() { - $("gate-container").style.display = "block"; - $("session-bar").style.display = "none"; - $("live-toggle-section").style.display = "none"; - $("inbox-section").style.display = "none"; -} - -/* ---------------- Bewerbungen ---------------- */ -async function loadInbox() { - const session = getSession(); - const includeArchived = $("show-archived").checked; - const res = await apiCall(API_BASE_URL, "/admin/applications/list", { includeArchived }); - if (!res.ok) { - if (!getSession()) showGate(); - return; - } - renderInbox(res.applications, session); -} - -function renderInbox(applications, session) { - const list = $("inbox-list"); - if (!applications.length) { - list.innerHTML = `

    Keine Bewerbungen in dieser Ansicht.

    `; - return; - } - - list.innerHTML = applications - .map((a) => { - const fields = a.data - ? Object.entries(a.data).map(([k, v]) => `

    ${k}: ${v}

    `).join("") - : `

    Keine Berechtigung, den vollen Inhalt zu öffnen.

    `; - const date = new Date(a.createdAt).toLocaleString("de-DE"); - - const actions = []; - if (sessionHasPermission(session, "APPLICATIONS_TAKE_OVER")) { - actions.push(``); - } - if (sessionHasPermission(session, "APPLICATIONS_MARK_ANSWERED")) { - actions.push(``); - } - if (sessionHasPermission(session, "APPLICATIONS_ARCHIVE") && !a.archived) { - actions.push(``); - } - if (sessionHasPermission(session, "APPLICATIONS_RESTORE") && a.archived) { - actions.push(``); - } - if (sessionHasPermission(session, "APPLICATIONS_DELETE")) { - actions.push(``); - } - if (sessionHasPermission(session, "NOTES_READ")) { - actions.push(``); - } - - const statusSelect = sessionHasPermission(session, "APPLICATIONS_CHANGE_STATUS") - ? `` - : `${a.status}`; - - return ` -
    -
    - ${a.type}${a.archived ? " · archiviert" : ""} - ${statusSelect} -
    -

    ${date}

    -
    ${fields}
    -
    ${actions.join("")}
    -
    -

    Lädt…

    - ${ - sessionHasPermission(session, "NOTES_WRITE") - ? `
    - - -
    ` - : "" - } -
    -
    `; - }) - .join(""); - - list.querySelectorAll(".take-btn").forEach((b) => b.addEventListener("click", () => appAction("/admin/applications/take-over", b.dataset.id))); - list.querySelectorAll(".answered-btn").forEach((b) => b.addEventListener("click", () => appAction("/admin/applications/mark-answered", b.dataset.id))); - list.querySelectorAll(".archive-btn").forEach((b) => b.addEventListener("click", () => appAction("/admin/applications/archive", b.dataset.id))); - list.querySelectorAll(".restore-btn").forEach((b) => b.addEventListener("click", () => appAction("/admin/applications/restore", b.dataset.id))); - list.querySelectorAll(".delete-btn").forEach((b) => - b.addEventListener("click", () => { - if (confirm("Diese Bewerbung wirklich endgültig löschen?")) appAction("/admin/applications/delete", b.dataset.id); - }) - ); - list.querySelectorAll(".status-select").forEach((sel) => - sel.addEventListener("change", () => appAction("/admin/applications/status", sel.dataset.id, { status: sel.value }, false)) - ); - list.querySelectorAll(".notes-toggle-btn").forEach((b) => - b.addEventListener("click", () => { - const box = $(`notes-${b.dataset.id}`); - box.classList.toggle("open"); - if (box.classList.contains("open")) loadNotes(b.dataset.id); - }) - ); - list.querySelectorAll(".add-note-btn").forEach((b) => - b.addEventListener("click", async () => { - const textarea = document.querySelector(`.note-input[data-id="${b.dataset.id}"]`); - const text = textarea.value.trim(); - if (!text) return; - const res = await apiCall(API_BASE_URL, "/admin/notes/add", { applicationId: b.dataset.id, text }); - if (res.ok) { - textarea.value = ""; - loadNotes(b.dataset.id); - } - }) - ); -} - -async function appAction(path, id, extra, reload = true) { - const res = await apiCall(API_BASE_URL, path, { id, ...(extra || {}) }); - if (res.ok && reload) loadInbox(); - else if (!res.ok) alert(res.error || "Fehler."); -} - -async function loadNotes(applicationId) { - const box = $(`notes-list-${applicationId}`); - const res = await apiCall(API_BASE_URL, "/admin/notes/list", { applicationId }); - if (!res.ok) { - box.innerHTML = `

    Konnte Notizen nicht laden.

    `; - return; - } - box.innerHTML = - res.notes - .map((n) => `
    ${n.author}: ${n.text}
    ${new Date(n.created_at).toLocaleString("de-DE")}
    `) - .join("") || `

    Noch keine Notizen.

    `; -} - -/* ---------------- Live-Status ---------------- - Der Status kommt automatisch von TikTok (Cron im Worker, jede Minute). - Die Handschaltung ist nur ein zeitlich begrenzter Vorrang. - ------------------------------------------------------------------- */ -function formatSince(iso) { - if (!iso) return null; - const diffMin = Math.floor((Date.now() - new Date(iso).getTime()) / 60000); - if (diffMin < 1) return "gerade eben"; - if (diffMin === 1) return "vor 1 Minute"; - if (diffMin < 60) return `vor ${diffMin} Minuten`; - const h = Math.floor(diffMin / 60); - return h === 1 ? "vor 1 Stunde" : `vor ${h} Stunden`; -} - -function renderLiveStatus(data) { - const live = !!(data && data.live); - const dot = $("admin-live-dot"); - dot.classList.remove("is-unknown"); - dot.classList.toggle("is-live", live); - $("admin-live-text").textContent = live ? "Jetzt live" : "Gerade offline"; - - const manual = data && data.source === "manuell"; - $("go-auto-btn").style.display = manual ? "inline-flex" : "none"; - - const lines = []; - if (manual) { - lines.push("⚠️ Von Hand gesetzt — die Automatik ist gerade übersteuert."); - if (data.overrideUntil) { - const until = new Date(data.overrideUntil); - lines.push(`Läuft aus um ${until.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" })} Uhr.`); - } - if (data.autoLive !== null && data.autoLive !== undefined) { - lines.push(`TikTok meldet gerade: ${data.autoLive ? "live" : "offline"}.`); - } - } else { - if (data && data.title && live) lines.push(`Titel: ${data.title}`); - const since = formatSince(data && data.autoCheckedAt); - if (since) lines.push(`Automatisch geprüft ${since}.`); - if (data && data.autoHealthy === false) { - lines.push("⚠️ Der letzte Abruf bei TikTok hat nicht geklappt — angezeigt wird der letzte bekannte Stand."); - } - } - $("admin-live-detail").innerHTML = lines.join("
    "); -} - -async function loadLiveStatus() { - try { - const res = await fetch(`${API_BASE_URL}/live-status`, { cache: "no-store" }); - const data = await res.json(); - if (res.ok && data.ok) renderLiveStatus(data); - } catch { - /* keine Blockade */ - } -} - -async function setLiveStatus(live) { - const res = await apiCall(API_BASE_URL, "/live-status/set", { live }); - if (res.ok) renderLiveStatus(res); -} - -async function backToAutoLive() { - const res = await apiCall(API_BASE_URL, "/live-status/set", { auto: true }); - if (res.ok) renderLiveStatus(res); -} - -/* ---------------- Login / Logout ---------------- */ -document.addEventListener("DOMContentLoaded", () => { - const existing = getSession(); - if (existing) showApp(existing); - else showGate(); - - $("code-gate").addEventListener("submit", async (e) => { - e.preventDefault(); - const status = $("gate-status"); - status.className = "form-status show"; - status.textContent = "Prüfe Code …"; - const res = await doLogin(API_BASE_URL, $("code-input").value); - if (res.ok) { - status.className = "form-status"; - showApp(getSession()); - } else { - status.textContent = res.error || "Falscher Code."; - status.className = "form-status show error"; - } - }); - - $("logout-btn").addEventListener("click", async () => { - await doLogout(API_BASE_URL); - showGate(); - }); - - $("refresh-btn").addEventListener("click", loadInbox); - $("show-archived").addEventListener("change", loadInbox); - $("go-live-btn").addEventListener("click", () => setLiveStatus(true)); - $("go-offline-btn").addEventListener("click", () => setLiveStatus(false)); - $("go-auto-btn").addEventListener("click", backToAutoLive); - - // Anzeige alle 30 Sekunden auffrischen, damit man die Automatik arbeiten sieht. - setInterval(() => { - if (!document.hidden && getSession()) loadLiveStatus(); - }, 30000); -}); diff --git a/assets/js/zugaenge.js b/assets/js/zugaenge.js deleted file mode 100644 index 7eadcb2..0000000 --- a/assets/js/zugaenge.js +++ /dev/null @@ -1,543 +0,0 @@ -/* ===================================================================== - zugaenge.js — Logik für zugaenge.html ("Zugänge und Teammitglieder") - - WICHTIG: Diese Permission-Liste muss exakt mit - cloudflare-worker/src/lib/permissions.js übereinstimmen (Anzeige only — - die eigentliche Durchsetzung passiert immer serverseitig im Worker). - ===================================================================== */ - -// Gleiche Worker-URL wie überall sonst (forms.js, postfach.html, index.html). -const API_BASE_URL = "https://dogfather-universe-postfach.dogfather1608.workers.dev"; - -const PERMISSION_GROUPS = [ - { label: "Bewerbungsbereiche", keys: ["APPLICATIONS_VIEW_CREATOR", "APPLICATIONS_VIEW_MANAGER_SCOUT", "APPLICATIONS_VIEW_ARCHIVED", "APPLICATIONS_OPEN_FULL"] }, - { label: "Bearbeitung", keys: ["APPLICATIONS_TAKE_OVER", "APPLICATIONS_ASSIGN", "APPLICATIONS_CHANGE_STATUS", "APPLICATIONS_ARCHIVE", "APPLICATIONS_RESTORE", "APPLICATIONS_DELETE"] }, - { label: "Antworten", keys: ["DRAFTS_CREATE", "DRAFTS_EDIT_OWN", "DRAFTS_EDIT_OTHERS", "REPLIES_COPY", "EMAILS_COPY", "APPLICATIONS_MARK_ANSWERED"] }, - { label: "Interne Notizen", keys: ["NOTES_READ", "NOTES_WRITE", "NOTES_EDIT_OWN", "NOTES_EDIT_OTHERS", "NOTES_DELETE"] }, - { label: "Teamverwaltung", keys: ["TEAM_VIEW", "TEAM_PREPARE_USER", "TEAM_LOCK_USER", "TEAM_ASSIGN_ROLE", "TEAM_CREATE_ROLE", "TEAM_EDIT_PERMISSIONS"] }, - { label: "Einstellungen", keys: ["SETTINGS_EDIT_FORMS", "SETTINGS_EDIT_STATUS_TYPES", "SETTINGS_VIEW_STATS", "SETTINGS_VIEW_ACTIVITY_LOG", "SETTINGS_EDIT_GENERAL"] }, -]; -const PERMISSION_LABELS = { - APPLICATIONS_VIEW_CREATOR: "Creator-Bewerbungen ansehen", - APPLICATIONS_VIEW_MANAGER_SCOUT: "Manager-/Scout-Bewerbungen ansehen", - APPLICATIONS_VIEW_ARCHIVED: "Archivierte Bewerbungen ansehen", - APPLICATIONS_OPEN_FULL: "Vollständige Bewerbungen öffnen", - APPLICATIONS_TAKE_OVER: "Bewerbungen übernehmen", - APPLICATIONS_ASSIGN: "Bewerbungen zuweisen", - APPLICATIONS_CHANGE_STATUS: "Status verändern", - APPLICATIONS_ARCHIVE: "Archivieren", - APPLICATIONS_RESTORE: "Wiederherstellen", - APPLICATIONS_DELETE: "Löschen", - DRAFTS_CREATE: "Antwortentwürfe erstellen", - DRAFTS_EDIT_OWN: "Eigene Entwürfe bearbeiten", - DRAFTS_EDIT_OTHERS: "Fremde Entwürfe bearbeiten", - REPLIES_COPY: "Antworten kopieren", - EMAILS_COPY: "E-Mail-Adressen kopieren", - APPLICATIONS_MARK_ANSWERED: "Als beantwortet markieren", - NOTES_READ: "Notizen lesen", - NOTES_WRITE: "Notizen schreiben", - NOTES_EDIT_OWN: "Eigene Notizen bearbeiten", - NOTES_EDIT_OTHERS: "Fremde Notizen bearbeiten", - NOTES_DELETE: "Notizen löschen", - TEAM_VIEW: "Teammitglieder ansehen", - TEAM_PREPARE_USER: "Neue Benutzer vorbereiten", - TEAM_LOCK_USER: "Benutzer sperren", - TEAM_ASSIGN_ROLE: "Rollen vergeben", - TEAM_CREATE_ROLE: "Rollen erstellen", - TEAM_EDIT_PERMISSIONS: "Berechtigungen verändern", - SETTINGS_EDIT_FORMS: "Bewerbungsformulare bearbeiten", - SETTINGS_EDIT_STATUS_TYPES: "Statusarten verändern", - SETTINGS_VIEW_STATS: "Statistiken ansehen", - SETTINGS_VIEW_ACTIVITY_LOG: "Aktivitätsverlauf ansehen", - SETTINGS_EDIT_GENERAL: "Allgemeine Einstellungen bearbeiten", -}; - -const { getSession, clearSession, sessionHasPermission, apiCall, doLogin, doLogout } = window.AdminAuth; - -let currentRoles = []; -let currentUsers = []; - -function $(id) { return document.getElementById(id); } - -function showApp(session) { - $("gate-container").style.display = "none"; - $("session-bar").style.display = "block"; - $("main-area").style.display = "block"; - $("role-badge").textContent = session.isOwner ? "Eingeloggt als: Dogi (Owner)" : `Eingeloggt als: ${session.name} (${session.roleName})`; - $("new-user-btn").style.display = session.isOwner || sessionHasPermission(session, "TEAM_PREPARE_USER") ? "inline-flex" : "none"; - $("new-role-btn").style.display = session.isOwner || sessionHasPermission(session, "TEAM_CREATE_ROLE") ? "inline-flex" : "none"; - const auditTabBtn = document.querySelector('[data-tab="tab-audit"]'); - const canViewAudit = session.isOwner || sessionHasPermission(session, "SETTINGS_VIEW_ACTIVITY_LOG"); - if (auditTabBtn) auditTabBtn.style.display = canViewAudit ? "" : "none"; - - // "Mein Zugang" (eigenen Owner-Code ändern) gibt es nur für Dogi selbst. - const myCodeTabBtn = document.querySelector('[data-tab="tab-mycode"]'); - if (myCodeTabBtn) myCodeTabBtn.style.display = session.isOwner ? "" : "none"; - - loadRoles().then(loadUsers); - if (session.isOwner || sessionHasPermission(session, "SETTINGS_VIEW_ACTIVITY_LOG")) loadAudit(); -} - -function showGate() { - $("gate-container").style.display = "block"; - $("session-bar").style.display = "none"; - $("main-area").style.display = "none"; -} - -/* ---------------- Tabs ---------------- */ -function setupTabs() { - document.querySelectorAll(".tab-btn").forEach((btn) => { - btn.addEventListener("click", () => { - document.querySelectorAll(".tab-btn").forEach((b) => b.classList.remove("active")); - document.querySelectorAll(".tab-panel").forEach((p) => p.classList.remove("active")); - btn.classList.add("active"); - $(btn.dataset.tab).classList.add("active"); - }); - }); -} - -/* ---------------- Users ---------------- */ -async function loadUsers() { - const res = await apiCall(API_BASE_URL, "/admin/users/list", {}); - if (!res.ok) return; - currentUsers = res.users; - renderUsers(); -} - -function roleNameById(id) { - const r = currentRoles.find((x) => x.id === id); - return r ? r.name : "—"; -} - -function renderUsers() { - const session = getSession(); - const container = $("users-list"); - if (!currentUsers.length) { - container.innerHTML = `

    Noch keine Zugänge angelegt.

    `; - return; - } - container.innerHTML = currentUsers - .map((u) => { - const statusClass = u.status === "active" ? "active" : u.status === "pending" ? "pending" : "locked"; - const isOwner = session.isOwner; - const canLock = isOwner || sessionHasPermission(session, "TEAM_LOCK_USER"); - const canAssignRole = isOwner || sessionHasPermission(session, "TEAM_ASSIGN_ROLE"); - const canEditPerms = isOwner || sessionHasPermission(session, "TEAM_EDIT_PERMISSIONS"); - - const actions = []; - if (u.status === "pending" && isOwner) { - actions.push(``); - } - if (isOwner && u.status !== "pending") { - actions.push(``); - actions.push(``); - actions.push(``); - } - if (canLock && u.status !== "pending") { - actions.push( - u.status === "active" - ? `` - : `` - ); - } - if (canAssignRole) { - actions.push(``); - } - if (canEditPerms) { - actions.push(``); - } - if (isOwner) { - actions.push(``); - } - - return ` -
    -
    ${u.name}
    @${u.username}${u.email ? " · " + u.email : ""}
    -
    ${roleNameById(u.roleId)}
    -
    ${u.status}
    -
    ${u.expiresAt ? new Date(u.expiresAt).toLocaleDateString("de-DE") : "dauerhaft"}
    -
    ${u.lastLoginAt ? new Date(u.lastLoginAt).toLocaleString("de-DE") : "nie"}
    -
    ${actions.join("")}
    -
    `; - }) - .join(""); - - container.querySelectorAll(".lock-btn").forEach((b) => b.addEventListener("click", () => userAction("/admin/users/lock", b.dataset.id))); - container.querySelectorAll(".unlock-btn").forEach((b) => b.addEventListener("click", () => userAction("/admin/users/unlock", b.dataset.id))); - container.querySelectorAll(".delete-btn").forEach((b) => - b.addEventListener("click", () => { - if (confirm("Diesen Zugang wirklich vollständig löschen?")) userAction("/admin/users/delete", b.dataset.id); - }) - ); - container.querySelectorAll(".endsessions-btn").forEach((b) => b.addEventListener("click", () => userAction("/admin/users/end-sessions", b.dataset.id, "Alle Geräte wurden abgemeldet."))); - container.querySelectorAll(".reveal-btn").forEach((b) => b.addEventListener("click", () => openRevealModal(b.dataset.id))); - container.querySelectorAll(".newcode-btn").forEach((b) => b.addEventListener("click", () => newCodeForUser(b.dataset.id))); - container.querySelectorAll(".finalize-btn").forEach((b) => b.addEventListener("click", () => openUserModal({ pendingUserId: b.dataset.id }))); - container.querySelectorAll(".role-btn").forEach((b) => b.addEventListener("click", () => changeUserRole(b.dataset.id))); - container.querySelectorAll(".perms-btn").forEach((b) => b.addEventListener("click", () => openPermModal(b.dataset.id))); -} - -async function userAction(path, id, successMsg) { - const res = await apiCall(API_BASE_URL, path, { id }); - if (res.ok) { - if (successMsg) alert(successMsg); - loadUsers(); - } else { - alert(res.error || "Fehler."); - } -} - -async function newCodeForUser(id) { - if (!confirm("Neuen Code erstellen? Der alte Code funktioniert danach sofort nicht mehr.")) return; - const res = await apiCall(API_BASE_URL, "/admin/users/set-code", { id }); - if (res.ok) { - alert(`Neuer Code: ${res.code}\n\nJetzt sichern/weitergeben — wird nirgends mehr angezeigt!`); - } else { - alert(res.error || "Fehler."); - } -} - -function changeUserRole(id) { - const options = currentRoles.map((r) => `${r.name} (${r.id})`).join("\n"); - const pick = prompt(`Neue Rolle für diese Person — Rollenname eingeben:\n\nVerfügbar:\n${currentRoles.map((r) => r.name).join(", ")}`); - if (pick === null) return; - const role = currentRoles.find((r) => r.name.toLowerCase() === pick.trim().toLowerCase()); - if (!role) return alert("Rolle nicht gefunden."); - apiCall(API_BASE_URL, "/admin/users/set-role", { id, roleId: role.id }).then((res) => { - if (res.ok) loadUsers(); - else alert(res.error || "Fehler."); - }); -} - -/* ---------------- Neuer Zugang / Vorbereiten ---------------- */ -function openUserModal({ pendingUserId } = {}) { - const session = getSession(); - $("user-form").reset(); - $("uf-new-code").style.display = "none"; - $("uf-pending-id").value = pendingUserId || ""; - $("user-modal-title").textContent = pendingUserId ? "Zugang freigeben" : session.isOwner ? "Neuer Zugang" : "Neuen Benutzer vorbereiten"; - $("uf-submit-btn").textContent = pendingUserId ? "Freigeben & Code erstellen" : session.isOwner ? "Zugang erstellen" : "Zur Freigabe vorbereiten"; - $("uf-code-field").style.display = session.isOwner ? "block" : "none"; - - const roleSelect = $("uf-role"); - roleSelect.innerHTML = currentRoles.map((r) => ``).join(""); - - if (pendingUserId) { - const u = currentUsers.find((x) => x.id === pendingUserId); - if (u) { - $("uf-name").value = u.name; - $("uf-username").value = u.username; - $("uf-email").value = u.email || ""; - if (u.roleId) roleSelect.value = u.roleId; - } - } - - $("user-modal").classList.add("open"); -} - -$("user-modal-close").addEventListener("click", () => $("user-modal").classList.remove("open")); -$("new-user-btn").addEventListener("click", () => openUserModal()); - -document.querySelectorAll('input[name="uf-code-mode"]').forEach((el) => { - el.addEventListener("change", () => { - $("uf-code-manual").style.display = document.querySelector('input[name="uf-code-mode"]:checked').value === "manual" ? "block" : "none"; - }); -}); -$("uf-expiry").addEventListener("change", () => { - $("uf-expiry-date").style.display = $("uf-expiry").value === "date" ? "block" : "none"; -}); - -$("user-form").addEventListener("submit", async (e) => { - e.preventDefault(); - const session = getSession(); - const pendingUserId = $("uf-pending-id").value || null; - const isOwner = session.isOwner; - - const payload = { - name: $("uf-name").value.trim(), - username: $("uf-username").value.trim(), - email: $("uf-email").value.trim(), - roleId: $("uf-role").value, - note: $("uf-note").value.trim(), - }; - - const statusEl = $("user-form-status"); - - if (!isOwner && !pendingUserId) { - const res = await apiCall(API_BASE_URL, "/admin/users/prepare", payload); - if (res.ok) { - statusEl.textContent = "Vorbereitet — Dogi muss den Zugang jetzt noch mit einem Code freigeben."; - statusEl.className = "form-status show success"; - loadUsers(); - setTimeout(() => $("user-modal").classList.remove("open"), 1800); - } else { - statusEl.textContent = res.error || "Fehler."; - statusEl.className = "form-status show error"; - } - return; - } - - if (pendingUserId) payload.pendingUserId = pendingUserId; - - const mode = document.querySelector('input[name="uf-code-mode"]:checked').value; - if (mode === "manual") payload.code = $("uf-code-manual").value.trim(); - - const expiryChoice = $("uf-expiry").value; - if (expiryChoice === "date") payload.expiresAt = $("uf-expiry-date").value; - else if (expiryChoice) payload.expiresInDays = Number(expiryChoice); - - const res = await apiCall(API_BASE_URL, "/admin/users/finalize", payload); - if (res.ok) { - $("uf-new-code").style.display = "block"; - $("uf-new-code").innerHTML = `Code: ${res.code}
    Jetzt sichern/weitergeben — wird nirgends mehr angezeigt!`; - statusEl.textContent = ""; - statusEl.className = "form-status"; - loadUsers(); - } else { - statusEl.textContent = res.error || "Fehler."; - statusEl.className = "form-status show error"; - } -}); - -/* ---------------- Code anzeigen (Re-Auth) ---------------- */ -let revealTargetId = null; -function openRevealModal(id) { - revealTargetId = id; - $("reveal-form").reset(); - $("reveal-code-box").style.display = "none"; - $("reveal-status").className = "form-status"; - $("reveal-modal").classList.add("open"); -} -$("reveal-modal-close").addEventListener("click", () => $("reveal-modal").classList.remove("open")); -$("reveal-form").addEventListener("submit", async (e) => { - e.preventDefault(); - const res = await apiCall(API_BASE_URL, "/admin/users/reveal-code", { - id: revealTargetId, - reAuthCode: $("reveal-reauth").value, - }); - if (res.ok) { - $("reveal-code-box").style.display = "block"; - $("reveal-code-box").textContent = res.code; - $("reveal-status").className = "form-status"; - } else { - $("reveal-status").textContent = res.error || "Fehler."; - $("reveal-status").className = "form-status show error"; - } -}); - -/* ---------------- Individuelle Rechte ---------------- */ -let permTargetId = null; -function openPermModal(id) { - permTargetId = id; - const user = currentUsers.find((u) => u.id === id); - const role = currentRoles.find((r) => r.id === user?.roleId); - const roleDefaults = role ? role.permissions : []; - - $("perm-matrix").innerHTML = PERMISSION_GROUPS.map( - (g) => ` -
    ${g.label}
    - - ${g.keys - .map((k) => { - const inRole = roleDefaults.includes(k); - return ` - - - - `; - }) - .join("")} -
    ${PERMISSION_LABELS[k]} ${inRole ? "(in Rolle enthalten)" : ""} - -
    ` - ).join(""); - - $("perm-status").className = "form-status"; - $("perm-modal").classList.add("open"); -} -$("perm-modal-close").addEventListener("click", () => $("perm-modal").classList.remove("open")); -$("perm-save-btn").addEventListener("click", async () => { - const overrides = {}; - document.querySelectorAll("#perm-matrix select[data-perm]").forEach((sel) => { - if (sel.value === "allow") overrides[sel.dataset.perm] = true; - else if (sel.value === "deny") overrides[sel.dataset.perm] = false; - }); - const res = await apiCall(API_BASE_URL, "/admin/users/set-permissions", { id: permTargetId, overrides }); - if (res.ok) { - $("perm-modal").classList.remove("open"); - } else { - $("perm-status").textContent = res.error || "Fehler."; - $("perm-status").className = "form-status show error"; - } -}); - -/* ---------------- Rollen ---------------- */ -async function loadRoles() { - const res = await apiCall(API_BASE_URL, "/admin/roles/list", {}); - if (!res.ok) return; - currentRoles = res.roles; - renderRoles(); -} - -function renderRoles() { - const session = getSession(); - const canManage = session.isOwner || sessionHasPermission(session, "TEAM_CREATE_ROLE") || sessionHasPermission(session, "TEAM_EDIT_PERMISSIONS"); - const container = $("roles-list"); - if (!currentRoles.length) { - container.innerHTML = `

    Noch keine Rollen erstellt.

    `; - return; - } - container.innerHTML = currentRoles - .map( - (r) => ` -
    -

    ${r.name}

    -

    ${r.permissions.length} Rechte aktiv

    - ${ - canManage - ? `
    - - -
    ` - : "" - } -
    ` - ) - .join(""); - - container.querySelectorAll(".edit-role-btn").forEach((b) => b.addEventListener("click", () => openRoleModal(b.dataset.id))); - container.querySelectorAll(".delete-role-btn").forEach((b) => - b.addEventListener("click", async () => { - if (!confirm("Rolle wirklich löschen?")) return; - const res = await apiCall(API_BASE_URL, "/admin/roles/delete", { id: b.dataset.id }); - if (res.ok) loadRoles(); - else alert(res.error || "Fehler."); - }) - ); -} - -function openRoleModal(id) { - const role = id ? currentRoles.find((r) => r.id === id) : null; - $("role-modal-title").textContent = role ? "Rolle bearbeiten" : "Neue Rolle"; - $("rf-id").value = id || ""; - $("rf-name").value = role ? role.name : ""; - $("role-form-status").className = "form-status"; - - $("role-perm-matrix").innerHTML = PERMISSION_GROUPS.map( - (g) => ` -
    ${g.label}
    - ${g.keys - .map( - (k) => ` - ` - ) - .join("")}` - ).join(""); - - $("role-modal").classList.add("open"); -} -$("new-role-btn").addEventListener("click", () => openRoleModal(null)); -$("role-modal-close").addEventListener("click", () => $("role-modal").classList.remove("open")); -$("role-save-btn").addEventListener("click", async () => { - const id = $("rf-id").value || null; - const name = $("rf-name").value.trim(); - if (!name) return; - const permissions = [...document.querySelectorAll("#role-perm-matrix input[data-perm]:checked")].map((el) => el.dataset.perm); - - const res = await apiCall(API_BASE_URL, id ? "/admin/roles/update" : "/admin/roles/create", id ? { id, name, permissions } : { name, permissions }); - if (res.ok) { - $("role-modal").classList.remove("open"); - loadRoles(); - } else { - $("role-form-status").textContent = res.error || "Fehler."; - $("role-form-status").className = "form-status show error"; - } -}); - -/* ---------------- Aktivitätsprotokoll ---------------- */ -async function loadAudit() { - const res = await apiCall(API_BASE_URL, "/admin/audit/list", { limit: 150 }); - if (!res.ok) return; - $("audit-list").innerHTML = - res.entries - .map( - (e) => ` -
    - ${e.actor} — ${e.action}${e.target ? ` (${e.target})` : ""} - ${new Date(e.created_at).toLocaleString("de-DE")} -
    ` - ) - .join("") || `

    Noch keine Einträge.

    `; -} - -/* ---------------- Login / Logout ---------------- */ -document.addEventListener("DOMContentLoaded", () => { - setupTabs(); - - const existing = getSession(); - if (existing) showApp(existing); - else showGate(); - - $("code-gate").addEventListener("submit", async (e) => { - e.preventDefault(); - const status = $("gate-status"); - status.className = "form-status show"; - status.textContent = "Prüfe Code …"; - const res = await doLogin(API_BASE_URL, $("code-input").value); - if (res.ok) { - status.className = "form-status"; - showApp(getSession()); - } else { - status.textContent = res.error || "Falscher Code."; - status.className = "form-status show error"; - } - }); - - $("logout-btn").addEventListener("click", async () => { - await doLogout(API_BASE_URL); - showGate(); - }); - - // Eigenen Hauptadministrator-Code ändern - $("owner-code-form").addEventListener("submit", async (e) => { - e.preventDefault(); - const status = $("owner-code-status"); - const currentCode = $("oc-current").value; - const newCode = $("oc-new").value; - const repeat = $("oc-new2").value; - - const fail = (msg) => { - status.textContent = msg; - status.className = "form-status show error"; - }; - - if (newCode !== repeat) return fail("Die beiden neuen Codes sind nicht gleich."); - if (newCode.length < 10) return fail("Der neue Code muss mindestens 10 Zeichen haben."); - - status.textContent = "Wird geändert …"; - status.className = "form-status show"; - - const res = await apiCall(API_BASE_URL, "/admin/owner/set-code", { currentCode, newCode }); - if (res.ok) { - status.textContent = "Erledigt — ab sofort gilt dein neuer Code. Gut merken! 🩵"; - status.className = "form-status show success"; - $("owner-code-form").reset(); - } else { - fail(res.error || "Hat nicht geklappt."); - } - }); - - // Modals per Klick auf Backdrop schließen - document.querySelectorAll(".modal-backdrop").forEach((m) => { - m.addEventListener("click", (e) => { - if (e.target === m) m.classList.remove("open"); - }); - }); -}); diff --git a/postfach.html b/postfach.html deleted file mode 100644 index 3543bea..0000000 --- a/postfach.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - -Postfach — DOGFATHER UNIVERSE - - - - - - - - - - - -
    -
    -
    - 🔒 Intern -

    Bewerbungs-Postfach

    -

    Nur für Dogfather & ausgewählte Personen mit Code.

    -
    -
    - - -
    -
    -
    -
    - - -
    - -
    -
    -

    - Zugänge, Rollen und Rechte fürs Team verwaltest du unter - Zugänge & Teammitglieder → -

    -
    -
    - - - - - - - - - -
    - - - - - - - diff --git a/robots.txt b/robots.txt index 9a65a83..54d3809 100644 --- a/robots.txt +++ b/robots.txt @@ -4,7 +4,6 @@ User-agent: * Allow: / -Disallow: /postfach.html Disallow: /cloudflare-worker/ Sitemap: https://REPLACE-WITH-YOUR-DOMAIN.tld/sitemap.xml diff --git a/zugaenge.html b/zugaenge.html deleted file mode 100644 index 5ac2766..0000000 --- a/zugaenge.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - -Zugänge und Teammitglieder — DOGFATHER UNIVERSE - - - - - - - - - - - -
    -
    -
    - 🔒 Interner Bereich -

    Zugänge und Teammitglieder

    -

    Rollen, Rechte, Codes — vollständig unter Dogis Kontrolle.

    -
    -
    - - -
    -
    -
    -
    - - -
    - -
    -
    -

    - ← Zurück zum Postfach -

    -
    -
    - - - - - - -
    - - - - - - - - - - - - - - - - - - -