Erster Commit
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Berechtigungen & Team</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: #111;
|
||||
color: #eee;
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 24px;
|
||||
max-width: 900px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
a.back { color: #6fbf73; text-decoration: none; font-size: 14px; }
|
||||
a.back:hover { text-decoration: underline; }
|
||||
h1 { margin-top: 10px; }
|
||||
|
||||
.box { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 18px; margin-bottom: 20px; }
|
||||
.box h2 { margin-top: 0; font-size: 16px; border-bottom: 1px solid #333; padding-bottom: 8px; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 8px; }
|
||||
th, td { text-align: left; padding: 8px 6px; border-bottom: 1px solid #2a2a2a; font-size: 13px; }
|
||||
th { color: #999; font-weight: normal; }
|
||||
.group-color-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; }
|
||||
|
||||
input, select {
|
||||
background: #222; border: 1px solid #444; color: #eee;
|
||||
padding: 7px 9px; border-radius: 4px; font-size: 13px;
|
||||
}
|
||||
button {
|
||||
background: #2c7a3d; border: none; color: white; padding: 7px 14px;
|
||||
border-radius: 4px; cursor: pointer; font-size: 13px;
|
||||
}
|
||||
button.secondary { background: #555; }
|
||||
button.danger { background: #a83232; }
|
||||
button:hover { opacity: 0.85; }
|
||||
|
||||
.form-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
|
||||
|
||||
.perm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 6px 16px; margin: 12px 0; }
|
||||
.perm-item { display: flex; align-items: center; gap: 8px; font-size: 13px; }
|
||||
.perm-item input { width: auto; }
|
||||
|
||||
.msg { font-size: 13px; min-height: 18px; margin-top: 8px; }
|
||||
.hidden { display: none !important; }
|
||||
|
||||
#accessDeniedBox { text-align: center; padding: 60px 20px; color: #e06c6c; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a class="back" href="/uebersicht.html">← Übersicht</a>
|
||||
<h1>🔑 Berechtigungen & Team</h1>
|
||||
|
||||
<div id="accessDeniedBox" class="hidden">
|
||||
<h2>Kein Zugriff</h2>
|
||||
<p>Du hast keine Berechtigung, Berechtigungsgruppen zu verwalten.</p>
|
||||
</div>
|
||||
|
||||
<div id="mainContent" class="hidden">
|
||||
<div class="box">
|
||||
<h2>Vorhandene Gruppen</h2>
|
||||
<table>
|
||||
<thead><tr><th>Gruppe</th><th>Rechte</th><th>Rang</th><th></th></tr></thead>
|
||||
<tbody id="groupsTableBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2 id="groupFormTitle">Neue Gruppe anlegen</h2>
|
||||
<div class="form-row">
|
||||
<label>Name <input type="text" id="groupName" placeholder="z.B. Moderator"></label>
|
||||
<label>Farbe <input type="color" id="groupColor" value="#6fbf73"></label>
|
||||
<label>Rang (höher = wichtiger, nur zur Sortierung) <input type="number" id="groupRank" value="0" style="width:80px;"></label>
|
||||
</div>
|
||||
|
||||
<div id="permCheckboxes" class="perm-grid"></div>
|
||||
|
||||
<div class="form-row">
|
||||
<button onclick="saveGroup()">Speichern</button>
|
||||
<button class="secondary" onclick="resetGroupForm()">Neue Gruppe (Formular leeren)</button>
|
||||
</div>
|
||||
<div class="msg" id="groupMsg"></div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2>Spieler einer Gruppe zuweisen</h2>
|
||||
<div class="form-row">
|
||||
<select id="playerSelect" style="min-width:220px;"></select>
|
||||
<select id="assignGroupSelect">
|
||||
<option value="">- keine Gruppe -</option>
|
||||
</select>
|
||||
<button onclick="assignSelectedPlayerGroup()">Zuweisen</button>
|
||||
</div>
|
||||
<p id="playerCurrentGroupHint" style="color:#888; font-size:12px; margin:6px 0 0;"></p>
|
||||
<div class="msg" id="assignMsg"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) {
|
||||
location.href = "/index.html";
|
||||
}
|
||||
|
||||
async function authFetch(url, options = {}) {
|
||||
options.headers = { ...(options.headers || {}), "Authorization": "Bearer " + token };
|
||||
const res = await fetch(url, options);
|
||||
return res;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text == null ? "" : String(text);
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
let permissionCatalog = [];
|
||||
let allGroups = [];
|
||||
let editingGroupId = null;
|
||||
|
||||
async function init() {
|
||||
const res = await authFetch("/api/admin/permission_catalog");
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
document.getElementById("accessDeniedBox").classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
permissionCatalog = data.catalog || [];
|
||||
renderPermCheckboxes();
|
||||
|
||||
document.getElementById("mainContent").classList.remove("hidden");
|
||||
await loadGroups();
|
||||
await loadAllPlayers();
|
||||
}
|
||||
|
||||
function renderPermCheckboxes() {
|
||||
const container = document.getElementById("permCheckboxes");
|
||||
container.innerHTML = permissionCatalog.map(p => `
|
||||
<label class="perm-item">
|
||||
<input type="checkbox" class="permCheckbox" value="${p.key}">
|
||||
${escapeHtml(p.label)}
|
||||
</label>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
async function loadGroups() {
|
||||
const res = await authFetch("/api/admin/permission_groups");
|
||||
const data = await res.json();
|
||||
allGroups = data.groups || [];
|
||||
|
||||
const tbody = document.getElementById("groupsTableBody");
|
||||
tbody.innerHTML = "";
|
||||
allGroups.forEach(g => {
|
||||
const tr = document.createElement("tr");
|
||||
const permLabels = g.permissions.map(key => {
|
||||
const found = permissionCatalog.find(p => p.key === key);
|
||||
return found ? found.label : key;
|
||||
});
|
||||
tr.innerHTML = `
|
||||
<td><span class="group-color-dot" style="background:${g.color};"></span>${escapeHtml(g.name)}</td>
|
||||
<td style="color:#999; font-size:12px;">${permLabels.length ? escapeHtml(permLabels.join(", ")) : "- keine -"}</td>
|
||||
<td>${g.rank}</td>
|
||||
<td>
|
||||
<button class="secondary" onclick="editGroup(${g.id})">Bearbeiten</button>
|
||||
<button class="danger" onclick="deleteGroup(${g.id})">Löschen</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
renderPlayerSelect();
|
||||
}
|
||||
|
||||
function editGroup(id) {
|
||||
const group = allGroups.find(g => g.id === id);
|
||||
if (!group) return;
|
||||
|
||||
editingGroupId = id;
|
||||
document.getElementById("groupFormTitle").textContent = "Gruppe bearbeiten: " + group.name;
|
||||
document.getElementById("groupName").value = group.name;
|
||||
document.getElementById("groupColor").value = group.color;
|
||||
document.getElementById("groupRank").value = group.rank;
|
||||
|
||||
document.querySelectorAll(".permCheckbox").forEach(cb => {
|
||||
cb.checked = group.permissions.includes(cb.value);
|
||||
});
|
||||
|
||||
window.scrollTo({ top: document.getElementById("groupFormTitle").offsetTop, behavior: "smooth" });
|
||||
}
|
||||
|
||||
function resetGroupForm() {
|
||||
editingGroupId = null;
|
||||
document.getElementById("groupFormTitle").textContent = "Neue Gruppe anlegen";
|
||||
document.getElementById("groupName").value = "";
|
||||
document.getElementById("groupColor").value = "#6fbf73";
|
||||
document.getElementById("groupRank").value = "0";
|
||||
document.querySelectorAll(".permCheckbox").forEach(cb => { cb.checked = false; });
|
||||
}
|
||||
|
||||
async function saveGroup() {
|
||||
const name = document.getElementById("groupName").value.trim();
|
||||
if (!name) {
|
||||
showMsg("groupMsg", "Name ist Pflicht.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
const permissions = [...document.querySelectorAll(".permCheckbox:checked")].map(cb => cb.value);
|
||||
const payload = {
|
||||
id: editingGroupId,
|
||||
name,
|
||||
color: document.getElementById("groupColor").value,
|
||||
rank: Number(document.getElementById("groupRank").value) || 0,
|
||||
permissions
|
||||
};
|
||||
|
||||
const res = await authFetch("/api/admin/permission_groups", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.ok) {
|
||||
showMsg("groupMsg", "Gespeichert.");
|
||||
resetGroupForm();
|
||||
await loadGroups();
|
||||
} else {
|
||||
showMsg("groupMsg", "Fehler: " + (data.error || "unbekannt"), true);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteGroup(id) {
|
||||
if (!confirm("Diese Gruppe wirklich löschen? Zugewiesene Spieler verlieren dann diese Rechte.")) return;
|
||||
const res = await authFetch("/api/admin/permission_groups/" + id, { method: "DELETE" });
|
||||
const data = await res.json();
|
||||
if (data.ok) {
|
||||
await loadGroups();
|
||||
} else {
|
||||
showMsg("groupMsg", "Löschen fehlgeschlagen.", true);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// SPIELER-ZUWEISUNG
|
||||
// -------------------------------------------------------------
|
||||
let allPlayers = [];
|
||||
|
||||
async function loadAllPlayers() {
|
||||
const res = await authFetch("/api/admin/player_group_search"); // ohne q -> alle Spieler
|
||||
const data = await res.json();
|
||||
allPlayers = data.players || [];
|
||||
renderPlayerSelect();
|
||||
}
|
||||
|
||||
function renderPlayerSelect() {
|
||||
const select = document.getElementById("playerSelect");
|
||||
const previousValue = select.value;
|
||||
|
||||
select.innerHTML = allPlayers.map(pl => {
|
||||
const currentGroup = allGroups.find(g => g.id === pl.permission_group_id);
|
||||
const suffix = pl.is_admin ? " 🛡️ Super-Admin" : (currentGroup ? ` (${currentGroup.name})` : " (keine Gruppe)");
|
||||
return `<option value="${pl.id}">${escapeHtml(pl.username)}${escapeHtml(suffix)}</option>`;
|
||||
}).join("");
|
||||
|
||||
if (previousValue) select.value = previousValue;
|
||||
|
||||
const groupSelect = document.getElementById("assignGroupSelect");
|
||||
groupSelect.innerHTML = `<option value="">- keine Gruppe -</option>` +
|
||||
allGroups.map(g => `<option value="${g.id}">${escapeHtml(g.name)}</option>`).join("");
|
||||
|
||||
updatePlayerCurrentGroupHint();
|
||||
}
|
||||
|
||||
function updatePlayerCurrentGroupHint() {
|
||||
const playerId = Number(document.getElementById("playerSelect").value);
|
||||
const pl = allPlayers.find(p => p.id === playerId);
|
||||
const hint = document.getElementById("playerCurrentGroupHint");
|
||||
if (!pl) { hint.textContent = ""; return; }
|
||||
|
||||
if (pl.is_admin) {
|
||||
hint.textContent = "Dieser Spieler ist Super-Admin und hat ohnehin immer vollen Zugriff, unabhängig von der Gruppe.";
|
||||
} else {
|
||||
const currentGroup = allGroups.find(g => g.id === pl.permission_group_id);
|
||||
hint.textContent = "Aktuelle Gruppe: " + (currentGroup ? currentGroup.name : "keine");
|
||||
}
|
||||
|
||||
document.getElementById("assignGroupSelect").value = pl.permission_group_id || "";
|
||||
}
|
||||
document.getElementById("playerSelect").addEventListener("change", updatePlayerCurrentGroupHint);
|
||||
|
||||
async function assignSelectedPlayerGroup() {
|
||||
const playerId = Number(document.getElementById("playerSelect").value);
|
||||
if (!playerId) {
|
||||
showMsg("assignMsg", "Bitte einen Spieler auswählen.", true);
|
||||
return;
|
||||
}
|
||||
const groupId = document.getElementById("assignGroupSelect").value || null;
|
||||
|
||||
const res = await authFetch("/api/admin/assign_player_group", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ playerId, groupId })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.ok) {
|
||||
showMsg("assignMsg", "Gruppe zugewiesen.");
|
||||
await loadAllPlayers();
|
||||
} else {
|
||||
showMsg("assignMsg", "Fehler: " + (data.error || "unbekannt"), true);
|
||||
}
|
||||
}
|
||||
|
||||
function showMsg(elId, text, isError) {
|
||||
const el = document.getElementById(elId);
|
||||
el.style.color = isError ? "#e06c6c" : "#6fbf73";
|
||||
el.textContent = text;
|
||||
setTimeout(() => { el.textContent = ""; }, 4000);
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user