- Footer hat jetzt einen eigenen Block "Intern" mit Links zum Bewerbungs-Postfach und zu Zugaenge & Rollen (vorher nur ein winziger Link ganz unten) - Postfach-Login verlinkt auf die Zugaenge-Seite - Neue Tabelle app_settings + Endpunkt /admin/owner/set-code: der Hauptadministrator kann seinen Code selbst aendern, mit Bestaetigung des aktuellen Codes; gespeichert wird nur der Hash - Neuer Tab "Mein Zugang" in zugaenge.html (nur fuer den Owner) Co-Authored-By: Claude Opus 5 <[email protected]>
544 lines
23 KiB
JavaScript
544 lines
23 KiB
JavaScript
/* =====================================================================
|
|
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 = `<p class="muted">Noch keine Zugänge angelegt.</p>`;
|
|
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(`<button class="btn btn-primary finalize-btn" data-id="${u.id}">Freigeben & Code erstellen</button>`);
|
|
}
|
|
if (isOwner && u.status !== "pending") {
|
|
actions.push(`<button class="btn btn-outline reveal-btn" data-id="${u.id}">Code anzeigen</button>`);
|
|
actions.push(`<button class="btn btn-outline newcode-btn" data-id="${u.id}">Neuer Code</button>`);
|
|
actions.push(`<button class="btn btn-outline endsessions-btn" data-id="${u.id}">Alle Geräte abmelden</button>`);
|
|
}
|
|
if (canLock && u.status !== "pending") {
|
|
actions.push(
|
|
u.status === "active"
|
|
? `<button class="btn btn-outline lock-btn" data-id="${u.id}">Sperren</button>`
|
|
: `<button class="btn btn-outline unlock-btn" data-id="${u.id}">Entsperren</button>`
|
|
);
|
|
}
|
|
if (canAssignRole) {
|
|
actions.push(`<button class="btn btn-outline role-btn" data-id="${u.id}">Rolle ändern</button>`);
|
|
}
|
|
if (canEditPerms) {
|
|
actions.push(`<button class="btn btn-outline perms-btn" data-id="${u.id}">Rechte verwalten</button>`);
|
|
}
|
|
if (isOwner) {
|
|
actions.push(`<button class="btn btn-outline delete-btn" data-id="${u.id}" style="color:var(--accent);">Löschen</button>`);
|
|
}
|
|
|
|
return `
|
|
<div class="user-row">
|
|
<div><strong>${u.name}</strong><br><span class="small muted">@${u.username}${u.email ? " · " + u.email : ""}</span></div>
|
|
<div>${roleNameById(u.roleId)}</div>
|
|
<div><span class="status-pill ${statusClass}">${u.status}</span></div>
|
|
<div class="small muted">${u.expiresAt ? new Date(u.expiresAt).toLocaleDateString("de-DE") : "dauerhaft"}</div>
|
|
<div class="small muted">${u.lastLoginAt ? new Date(u.lastLoginAt).toLocaleString("de-DE") : "nie"}</div>
|
|
<div class="actions">${actions.join("")}</div>
|
|
</div>`;
|
|
})
|
|
.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) => `<option value="${r.id}">${r.name}</option>`).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 = `<strong>Code:</strong> ${res.code}<br><span class="small">Jetzt sichern/weitergeben — wird nirgends mehr angezeigt!</span>`;
|
|
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) => `
|
|
<div class="perm-group-title">${g.label}</div>
|
|
<table class="perm-table"><tbody>
|
|
${g.keys
|
|
.map((k) => {
|
|
const inRole = roleDefaults.includes(k);
|
|
return `
|
|
<tr>
|
|
<td>${PERMISSION_LABELS[k]} <span class="small muted">${inRole ? "(in Rolle enthalten)" : ""}</span></td>
|
|
<td>
|
|
<select data-perm="${k}" style="background:var(--bg-alt);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:.3rem;">
|
|
<option value="inherit">Rolle (${inRole ? "erlaubt" : "nicht erlaubt"})</option>
|
|
<option value="allow">Erlaubt</option>
|
|
<option value="deny">Verboten</option>
|
|
</select>
|
|
</td>
|
|
</tr>`;
|
|
})
|
|
.join("")}
|
|
</tbody></table>`
|
|
).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 = `<p class="muted">Noch keine Rollen erstellt.</p>`;
|
|
return;
|
|
}
|
|
container.innerHTML = currentRoles
|
|
.map(
|
|
(r) => `
|
|
<div class="card">
|
|
<h3>${r.name}</h3>
|
|
<p class="small muted">${r.permissions.length} Rechte aktiv</p>
|
|
${
|
|
canManage
|
|
? `<div class="btn-row" style="margin-top:.6rem;">
|
|
<button class="btn btn-outline edit-role-btn" data-id="${r.id}">Bearbeiten</button>
|
|
<button class="btn btn-outline delete-role-btn" data-id="${r.id}" style="color:var(--accent);">Löschen</button>
|
|
</div>`
|
|
: ""
|
|
}
|
|
</div>`
|
|
)
|
|
.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) => `
|
|
<div class="perm-group-title">${g.label}</div>
|
|
${g.keys
|
|
.map(
|
|
(k) => `
|
|
<label class="small" style="display:flex;gap:.5rem;align-items:center;padding:.25rem 0;">
|
|
<input type="checkbox" data-perm="${k}" ${role && role.permissions.includes(k) ? "checked" : ""} />
|
|
${PERMISSION_LABELS[k]}
|
|
</label>`
|
|
)
|
|
.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) => `
|
|
<div class="audit-row">
|
|
<strong>${e.actor}</strong> — ${e.action}${e.target ? ` <span class="small muted">(${e.target})</span>` : ""}
|
|
<span class="small muted" style="float:right;">${new Date(e.created_at).toLocaleString("de-DE")}</span>
|
|
</div>`
|
|
)
|
|
.join("") || `<p class="muted">Noch keine Einträge.</p>`;
|
|
}
|
|
|
|
/* ---------------- 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");
|
|
});
|
|
});
|
|
});
|