Neue Helper-Funktionen: - _openCardMenu(trigger, dropdown): Card-Menü-Positionierung (4x in dashboard.js dedupliziert) - _createModal(title, extraClasses): Modal-Boilerplate (3 Modals in modals.js dedupliziert) - _openPickerModal(title, items, currentId, onSelect, opts): Picker-Modal (Agent + Session zusammengeführt) - createMDE(element, opts): EasyMDE-Initialisierung (3x in agent/session/profile-page dedupliziert) Weitere Bereinigungen: - _formatReplyTimestamp() entfernt, durch _formatTimestamp() ersetzt (reply.js + chat.js) - showFormMessage/showPageMessage in admin-page.js durch showToast() ersetzt - Inline-Nachrichten (profileMessage, profilePasswordMessage, adminMessage, adminFormMessage) durch Toast-Notifications ersetzt, zugehörige HTML-Elemente entfernt - profile-page.js: messageDiv/passwordMessageDiv-Logik durch showToast() ersetzt 11 Dateien, -210 Zeilen netto
293 lines
9.4 KiB
JavaScript
293 lines
9.4 KiB
JavaScript
// ── Admin Page (User Management) ──
|
|
|
|
registerPage('admin', {
|
|
async init() {
|
|
if (!currentUser || !currentUser.isAdmin) {
|
|
navigateTo('dashboard');
|
|
return;
|
|
}
|
|
|
|
var users = [];
|
|
var editingUserId = null;
|
|
|
|
var list = document.getElementById('adminUserList');
|
|
var newUserBtn = document.getElementById('adminNewUserBtn');
|
|
var userForm = document.getElementById('adminUserForm');
|
|
var formTitle = document.getElementById('adminFormTitle');
|
|
var usernameInput = document.getElementById('adminUsername');
|
|
var displayNameInput = document.getElementById('adminDisplayName');
|
|
var passwordInput = document.getElementById('adminPassword');
|
|
var passwordLabel = document.getElementById('adminPasswordLabel');
|
|
var defaultPathInput = document.getElementById('adminDefaultPath');
|
|
var defaultPathBrowse = document.getElementById('adminDefaultPathBrowse');
|
|
var isAdminCheckbox = document.getElementById('adminIsAdmin');
|
|
var saveBtn = document.getElementById('adminFormSaveBtn');
|
|
|
|
function renderUsers() {
|
|
list.innerHTML = '';
|
|
|
|
users.forEach(function(user) {
|
|
var row = document.createElement('div');
|
|
row.className = 'admin-user-row';
|
|
|
|
var info = document.createElement('div');
|
|
info.className = 'admin-user-info';
|
|
|
|
var nameSpan = document.createElement('span');
|
|
nameSpan.className = 'admin-user-name';
|
|
nameSpan.textContent = user.displayName || user.username;
|
|
|
|
var metaSpan = document.createElement('span');
|
|
metaSpan.className = 'admin-user-meta';
|
|
var metaParts = ['@' + user.username];
|
|
if (user.isAdmin) metaParts.push('Administrator');
|
|
if (user.defaultPath) metaParts.push(user.defaultPath);
|
|
metaSpan.textContent = metaParts.join(' · ');
|
|
|
|
info.appendChild(nameSpan);
|
|
info.appendChild(metaSpan);
|
|
|
|
var actions = document.createElement('div');
|
|
actions.className = 'admin-user-actions';
|
|
|
|
var editBtn = document.createElement('button');
|
|
editBtn.className = 'icon-btn';
|
|
editBtn.title = 'Bearbeiten';
|
|
editBtn.innerHTML = '<i class="fa fa-pencil"></i>';
|
|
editBtn.addEventListener('click', function() {
|
|
startEdit(user);
|
|
});
|
|
actions.appendChild(editBtn);
|
|
|
|
// Login-Link + Löschen (nicht für sich selbst)
|
|
if (user.userId !== currentUser.userId) {
|
|
var linkBtn = document.createElement('button');
|
|
linkBtn.className = 'icon-btn';
|
|
linkBtn.title = 'Login-Link generieren';
|
|
linkBtn.innerHTML = '<i class="fa fa-link"></i>';
|
|
linkBtn.addEventListener('click', function() {
|
|
generateLoginLink(user);
|
|
});
|
|
actions.appendChild(linkBtn);
|
|
|
|
var deleteBtn = document.createElement('button');
|
|
deleteBtn.className = 'icon-btn';
|
|
deleteBtn.title = 'Löschen';
|
|
deleteBtn.innerHTML = '<i class="fa fa-trash"></i>';
|
|
deleteBtn.addEventListener('click', function() {
|
|
deleteUser(user);
|
|
});
|
|
actions.appendChild(deleteBtn);
|
|
}
|
|
|
|
row.appendChild(info);
|
|
row.appendChild(actions);
|
|
list.appendChild(row);
|
|
});
|
|
}
|
|
|
|
async function loadUsers() {
|
|
try {
|
|
users = await apiAdminGetUsers();
|
|
renderUsers();
|
|
} catch (err) {
|
|
showToast(err.message, 'error');
|
|
}
|
|
}
|
|
|
|
function startEdit(user) {
|
|
editingUserId = user.userId;
|
|
formTitle.textContent = 'Benutzer bearbeiten';
|
|
usernameInput.value = user.username;
|
|
usernameInput.disabled = false;
|
|
displayNameInput.value = user.displayName || '';
|
|
passwordInput.value = '';
|
|
passwordLabel.textContent = 'Neues Passwort (leer = unverändert)';
|
|
passwordInput.placeholder = 'Neues Passwort (optional)';
|
|
passwordInput.required = false;
|
|
defaultPathInput.value = user.defaultPath || '';
|
|
isAdminCheckbox.checked = user.isAdmin;
|
|
saveBtn.textContent = 'Speichern';
|
|
userForm.hidden = false;
|
|
usernameInput.focus();
|
|
}
|
|
|
|
async function deleteUser(user) {
|
|
if (!confirm('Benutzer "' + (user.displayName || user.username) + '" wirklich löschen?\n\nAlle Tokens werden ebenfalls gelöscht.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await apiAdminDeleteUser(user.userId);
|
|
await loadUsers();
|
|
} catch (err) {
|
|
showToast(err.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function generateLoginLink(user) {
|
|
try {
|
|
var result = await apiAdminGetLoginLink(user.userId);
|
|
var link = window.location.origin + '/api/auth/autologin?token=' + result.token;
|
|
showLoginLinkModal(user, link);
|
|
} catch (err) {
|
|
showToast(err.message, 'error');
|
|
}
|
|
}
|
|
|
|
function showLoginLinkModal(user, link) {
|
|
// Entferne vorherigen Modal falls vorhanden
|
|
var existing = document.querySelector('.login-link-overlay');
|
|
if (existing) existing.remove();
|
|
|
|
var overlay = document.createElement('div');
|
|
overlay.className = 'login-link-overlay';
|
|
|
|
var modal = document.createElement('div');
|
|
modal.className = 'login-link-modal';
|
|
|
|
var title = document.createElement('h3');
|
|
title.textContent = 'Login-Link für ' + (user.displayName || user.username);
|
|
|
|
var desc = document.createElement('p');
|
|
desc.className = 'login-link-desc';
|
|
desc.textContent = 'Dieser Link loggt automatisch als dieser Benutzer ein. Teile ihn nur mit vertrauenswürdigen Personen.';
|
|
|
|
var inputRow = document.createElement('div');
|
|
inputRow.className = 'login-link-input-row';
|
|
|
|
var input = document.createElement('input');
|
|
input.type = 'text';
|
|
input.readOnly = true;
|
|
input.value = link;
|
|
input.className = 'login-link-input';
|
|
|
|
var copyBtn = document.createElement('button');
|
|
copyBtn.className = 'btn btn-primary';
|
|
copyBtn.innerHTML = '<i class="fa fa-copy"></i> Kopieren';
|
|
copyBtn.addEventListener('click', function() {
|
|
navigator.clipboard.writeText(link).then(function() {
|
|
copyBtn.innerHTML = '<i class="fa fa-check"></i> Kopiert!';
|
|
setTimeout(function() {
|
|
copyBtn.innerHTML = '<i class="fa fa-copy"></i> Kopieren';
|
|
}, 2000);
|
|
});
|
|
});
|
|
|
|
inputRow.appendChild(input);
|
|
inputRow.appendChild(copyBtn);
|
|
|
|
var closeBtn = document.createElement('button');
|
|
closeBtn.className = 'btn';
|
|
closeBtn.textContent = 'Schließen';
|
|
closeBtn.addEventListener('click', function() {
|
|
overlay.remove();
|
|
});
|
|
|
|
modal.appendChild(title);
|
|
modal.appendChild(desc);
|
|
modal.appendChild(inputRow);
|
|
modal.appendChild(closeBtn);
|
|
overlay.appendChild(modal);
|
|
|
|
overlay.addEventListener('click', function(e) {
|
|
if (e.target === overlay) overlay.remove();
|
|
});
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
// Input selektieren für einfaches Kopieren
|
|
input.select();
|
|
}
|
|
|
|
// ── Event-Listener ──
|
|
|
|
// Default Path Browse
|
|
if (defaultPathBrowse) {
|
|
defaultPathBrowse.addEventListener('click', function() {
|
|
openFolderBrowser({
|
|
startPath: defaultPathInput.value.trim() || '/',
|
|
onSelect: function(path) {
|
|
defaultPathInput.value = path;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Neuer Benutzer
|
|
newUserBtn.addEventListener('click', function() {
|
|
editingUserId = null;
|
|
formTitle.textContent = 'Neuer Benutzer';
|
|
usernameInput.value = '';
|
|
usernameInput.disabled = false;
|
|
displayNameInput.value = '';
|
|
passwordInput.value = '';
|
|
passwordLabel.textContent = 'Passwort';
|
|
passwordInput.placeholder = 'Passwort';
|
|
passwordInput.required = true;
|
|
defaultPathInput.value = '';
|
|
isAdminCheckbox.checked = false;
|
|
saveBtn.textContent = 'Erstellen';
|
|
userForm.hidden = false;
|
|
usernameInput.focus();
|
|
});
|
|
|
|
// Speichern
|
|
saveBtn.addEventListener('click', async function() {
|
|
saveBtn.disabled = true;
|
|
|
|
try {
|
|
if (editingUserId) {
|
|
// Update
|
|
var data = {
|
|
username: usernameInput.value.trim(),
|
|
displayName: displayNameInput.value.trim(),
|
|
defaultPath: defaultPathInput.value.trim() || null,
|
|
isAdmin: isAdminCheckbox.checked
|
|
};
|
|
await apiAdminUpdateUser(editingUserId, data);
|
|
|
|
// Passwort nur wenn ausgefüllt
|
|
var pw = passwordInput.value;
|
|
if (pw) {
|
|
await apiAdminResetPassword(editingUserId, pw);
|
|
}
|
|
|
|
showToast('Benutzer aktualisiert');
|
|
} else {
|
|
// Create
|
|
var username = usernameInput.value.trim();
|
|
var password = passwordInput.value;
|
|
|
|
if (!username || !password) {
|
|
showToast('Benutzername und Passwort erforderlich', 'error');
|
|
return;
|
|
}
|
|
|
|
await apiAdminCreateUser({
|
|
username: username,
|
|
password: password,
|
|
displayName: displayNameInput.value.trim(),
|
|
defaultPath: defaultPathInput.value.trim() || null,
|
|
isAdmin: isAdminCheckbox.checked
|
|
});
|
|
|
|
showToast('Benutzer erstellt');
|
|
}
|
|
|
|
userForm.hidden = true;
|
|
await loadUsers();
|
|
} catch (err) {
|
|
showToast(err.message, 'error');
|
|
} finally {
|
|
saveBtn.disabled = false;
|
|
}
|
|
});
|
|
|
|
// ── Init: User laden ──
|
|
await loadUsers();
|
|
},
|
|
|
|
destroy() {}
|
|
});
|