espid2sensor: User-Liste mit Passwort-Reset und Löschen im User-Tab
Ersetzt das manuelle E-Mail-Eingabefeld für den Passwort-Reset durch eine Liste aller angemeldeten User. Pro User stehen zwei Aktionen zur Verfügung: Passwort zurücksetzen (nutzt die bestehende /api/resetPassword-Route) und Löschen (neue DELETE /api/users/:id-Route, admin-only, verhindert Löschen des eigenen Accounts). Neue GET /api/users-Route liefert Email/Rolle aller User (admin-only).
This commit is contained in:
@@ -34,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
userResult.textContent = 'User erfolgreich angelegt!';
|
userResult.textContent = 'User erfolgreich angelegt!';
|
||||||
|
if (window.loadUsers) window.loadUsers();
|
||||||
} else {
|
} else {
|
||||||
userResult.textContent = data.error || 'Fehler beim Anlegen.';
|
userResult.textContent = data.error || 'Fehler beim Anlegen.';
|
||||||
}
|
}
|
||||||
@@ -77,34 +78,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetPasswordBtn = document.getElementById('resetPasswordBtn');
|
|
||||||
if (resetPasswordBtn) {
|
|
||||||
resetPasswordBtn.addEventListener('click', async () => {
|
|
||||||
const email = document.getElementById('resetEmail').value.trim();
|
|
||||||
const newPassword = document.getElementById('resetNewPassword').value;
|
|
||||||
const resetPasswordResult = document.getElementById('resetPasswordResult');
|
|
||||||
if (!email || !newPassword) {
|
|
||||||
resetPasswordResult.textContent = 'Email und neues Passwort erforderlich.';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch('/api/resetPassword', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ email, newPassword })
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.success) {
|
|
||||||
resetPasswordResult.textContent = 'Passwort erfolgreich zurückgesetzt!';
|
|
||||||
document.getElementById('resetPasswordForm').reset();
|
|
||||||
} else {
|
|
||||||
resetPasswordResult.textContent = data.error || 'Fehler beim Zurücksetzen.';
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
resetPasswordResult.textContent = 'Serverfehler.';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateSortArrows() {
|
function updateSortArrows() {
|
||||||
@@ -467,6 +440,73 @@ function showModal(message, showCancelButton, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadUsers() {
|
||||||
|
const usersTableBody = document.querySelector('#usersTable tbody');
|
||||||
|
const usersResult = document.getElementById('usersResult');
|
||||||
|
if (!usersTableBody) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/users');
|
||||||
|
const users = await res.json();
|
||||||
|
usersTableBody.innerHTML = '';
|
||||||
|
users.forEach(u => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>${u.email}</td>
|
||||||
|
<td>${u.role}</td>
|
||||||
|
<td>
|
||||||
|
<div class="twobuttons">
|
||||||
|
<button data-id="${u._id}" data-email="${u.email}" class="editBtn resetUserBtn" title="Passwort zurücksetzen">🔑</button>
|
||||||
|
<button data-id="${u._id}" class="deleteBtn deleteUserBtn" title="Löschen">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
usersTableBody.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.resetUserBtn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const email = btn.getAttribute('data-email');
|
||||||
|
const newPassword = prompt(`Neues Passwort für ${email}:`);
|
||||||
|
if (!newPassword) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/resetPassword', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ email, newPassword })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
usersResult.textContent = data.success ? `Passwort für ${email} zurückgesetzt.` : (data.error || 'Fehler beim Zurücksetzen.');
|
||||||
|
} catch (err) {
|
||||||
|
usersResult.textContent = 'Serverfehler.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.deleteUserBtn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const id = btn.getAttribute('data-id');
|
||||||
|
showModal('User wirklich löschen?', true, async (confirmed) => {
|
||||||
|
if (!confirmed) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/users/${id}`, { method: 'DELETE' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
usersResult.textContent = 'User gelöscht.';
|
||||||
|
await loadUsers();
|
||||||
|
} else {
|
||||||
|
usersResult.textContent = data.error || 'Fehler beim Löschen.';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
usersResult.textContent = 'Serverfehler.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
saveBtn.addEventListener('click', saveEntry);
|
saveBtn.addEventListener('click', saveEntry);
|
||||||
refreshBtn.addEventListener('click', loadEntries);
|
refreshBtn.addEventListener('click', loadEntries);
|
||||||
cancelBtn.addEventListener('click', () => clearForm(true));
|
cancelBtn.addEventListener('click', () => clearForm(true));
|
||||||
@@ -475,9 +515,10 @@ function showModal(message, showCancelButton, callback) {
|
|||||||
tabInput.addEventListener('click', () => showTab('input'))
|
tabInput.addEventListener('click', () => showTab('input'))
|
||||||
tabList.addEventListener('click', () => showTab('list'))
|
tabList.addEventListener('click', () => showTab('list'))
|
||||||
const tabUser = document.getElementById('tabUser');
|
const tabUser = document.getElementById('tabUser');
|
||||||
if (tabUser) tabUser.addEventListener('click', () => showTab('user'))
|
if (tabUser) tabUser.addEventListener('click', () => { showTab('user'); loadUsers(); })
|
||||||
|
|
||||||
loadEntries();
|
loadEntries();
|
||||||
|
window.loadUsers = loadUsers;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.showTab = showTab;
|
window.showTab = showTab;
|
||||||
@@ -127,4 +127,29 @@ export function registerApiRoutes(app, requireLogin) {
|
|||||||
res.status(500).json({ error: 'Fehler beim Zurücksetzen' });
|
res.status(500).json({ error: 'Fehler beim Zurücksetzen' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/api/users', requireLogin, async (req, res) => {
|
||||||
|
if (!req.session.isAdmin) return res.status(403).json({ error: 'Nur Admins erlaubt' });
|
||||||
|
try {
|
||||||
|
const users = await usersCollection.find({}, { projection: { email: 1, role: 1 } }).sort({ email: 1 }).toArray();
|
||||||
|
res.json(users);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: 'Fehler beim Laden der User' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/api/users/:id', requireLogin, async (req, res) => {
|
||||||
|
if (!req.session.isAdmin) return res.status(403).json({ error: 'Nur Admins erlaubt' });
|
||||||
|
const { id } = req.params;
|
||||||
|
if (id === String(req.session.userId)) {
|
||||||
|
return res.status(400).json({ error: 'Der eigene Account kann nicht gelöscht werden' });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await usersCollection.deleteOne({ _id: new ObjectId(id) });
|
||||||
|
if (result.deletedCount === 0) return res.status(404).json({ error: 'User nicht gefunden' });
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: 'Fehler beim Löschen' });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,15 +103,15 @@ html(lang="de")
|
|||||||
button#userCancelBtn(type="button") Abbrechen
|
button#userCancelBtn(type="button") Abbrechen
|
||||||
div#userResult
|
div#userResult
|
||||||
div.card
|
div.card
|
||||||
h2 Passwort eines Users zurücksetzen
|
h2 Angemeldete User
|
||||||
form#resetPasswordForm
|
table#usersTable
|
||||||
label(for="resetEmail") Email:
|
thead
|
||||||
input#resetEmail(type="email" required)
|
tr
|
||||||
label(for="resetNewPassword") Neues Passwort:
|
th Email
|
||||||
input#resetNewPassword(type="password" required)
|
th Rolle
|
||||||
.twobuttons
|
th Aktionen
|
||||||
button#resetPasswordBtn(type="button") Zurücksetzen
|
tbody
|
||||||
div#resetPasswordResult
|
div#usersResult
|
||||||
#version Version: #{version} vom #{vdate}
|
#version Version: #{version} vom #{vdate}
|
||||||
|
|
||||||
script(type="module" src="/global.js")
|
script(type="module" src="/global.js")
|
||||||
Reference in New Issue
Block a user