635 Zeilen
20 KiB
JavaScript
635 Zeilen
20 KiB
JavaScript
/**
|
|
* TASKMATE - Admin Dashboard
|
|
* ==========================
|
|
* Benutzerverwaltung fuer Administratoren
|
|
*/
|
|
|
|
import api from './api.js';
|
|
import { $, $$ } from './utils.js';
|
|
import authManager from './auth.js';
|
|
import store from './store.js';
|
|
|
|
class AdminManager {
|
|
constructor() {
|
|
this.users = [];
|
|
this.currentEditUser = null;
|
|
this.uploadSettings = null;
|
|
this.allowedExtensions = ['pdf', 'docx', 'txt'];
|
|
this.initialized = false;
|
|
|
|
// Vorschläge für häufige Dateiendungen
|
|
this.extensionSuggestions = [
|
|
'xlsx', 'pptx', 'doc', 'xls', 'ppt', // Office
|
|
'png', 'jpg', 'gif', 'svg', 'webp', // Bilder
|
|
'csv', 'json', 'xml', 'md', // Daten
|
|
'zip', 'rar', '7z', // Archive
|
|
'odt', 'ods', 'rtf' // OpenDocument
|
|
];
|
|
}
|
|
|
|
async init() {
|
|
console.log('[Admin] init() called, initialized:', this.initialized);
|
|
|
|
if (this.initialized) {
|
|
await this.loadUsers();
|
|
await this.loadUploadSettings();
|
|
return;
|
|
}
|
|
|
|
// DOM Elements - erst bei init() laden
|
|
this.adminScreen = $('#admin-screen');
|
|
console.log('[Admin] adminScreen found:', !!this.adminScreen);
|
|
this.usersList = $('#admin-users-list');
|
|
this.logoutBtn = $('#admin-logout-btn');
|
|
this.newUserBtn = $('#btn-new-user');
|
|
|
|
// Modal Elements
|
|
this.userModal = $('#user-modal');
|
|
this.userModalTitle = $('#user-modal-title');
|
|
this.userForm = $('#user-form');
|
|
this.editUserId = $('#edit-user-id');
|
|
this.usernameInput = $('#user-username');
|
|
this.displayNameInput = $('#user-displayname');
|
|
this.emailInput = $('#user-email');
|
|
this.passwordInput = $('#user-password');
|
|
this.passwordHint = $('#password-hint');
|
|
this.roleSelect = $('#user-role');
|
|
this.permissionsGroup = $('#permissions-group');
|
|
this.permGenehmigung = $('#perm-genehmigung');
|
|
this.deleteUserBtn = $('#btn-delete-user');
|
|
this.unlockUserBtn = $('#btn-unlock-user');
|
|
|
|
// Upload Settings Elements
|
|
this.uploadMaxSizeInput = $('#upload-max-size');
|
|
this.saveUploadSettingsBtn = $('#btn-save-upload-settings');
|
|
this.extensionTagsContainer = $('#extension-tags');
|
|
this.extensionInput = $('#extension-input');
|
|
this.addExtensionBtn = $('#btn-add-extension');
|
|
this.extensionSuggestionsList = $('#extension-suggestions-list');
|
|
|
|
this.bindEvents();
|
|
this.initialized = true;
|
|
|
|
await this.loadUsers();
|
|
await this.loadUploadSettings();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Logout
|
|
this.logoutBtn?.addEventListener('click', () => this.handleLogout());
|
|
|
|
// New User Button
|
|
this.newUserBtn?.addEventListener('click', () => this.openNewUserModal());
|
|
|
|
// User Form Submit
|
|
this.userForm?.addEventListener('submit', (e) => this.handleUserSubmit(e));
|
|
|
|
// Role Change - hide permissions for admin
|
|
this.roleSelect?.addEventListener('change', () => this.togglePermissionsVisibility());
|
|
|
|
// Delete User Button
|
|
this.deleteUserBtn?.addEventListener('click', () => this.handleDeleteUser());
|
|
|
|
// Unlock User Button
|
|
this.unlockUserBtn?.addEventListener('click', () => this.handleUnlockUser());
|
|
|
|
// Modal close buttons
|
|
this.userModal?.querySelectorAll('[data-close-modal]').forEach(btn => {
|
|
btn.addEventListener('click', () => this.closeModal());
|
|
});
|
|
|
|
// Upload Settings - Save Button
|
|
this.saveUploadSettingsBtn?.addEventListener('click', () => this.saveUploadSettings());
|
|
|
|
// Upload Settings - Add Extension
|
|
this.addExtensionBtn?.addEventListener('click', () => this.addExtensionFromInput());
|
|
|
|
// Enter-Taste im Input-Feld
|
|
this.extensionInput?.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
this.addExtensionFromInput();
|
|
}
|
|
});
|
|
|
|
// Password-Buttons
|
|
$('#edit-password-btn')?.addEventListener('click', () => this.togglePasswordEdit());
|
|
$('#generate-password-btn')?.addEventListener('click', () => this.generatePassword());
|
|
}
|
|
|
|
async loadUsers() {
|
|
try {
|
|
this.users = await api.getAdminUsers();
|
|
this.renderUsers();
|
|
} catch (error) {
|
|
console.error('Error loading users:', error);
|
|
this.showToast('Fehler beim Laden der Benutzer', 'error');
|
|
}
|
|
}
|
|
|
|
renderUsers() {
|
|
if (!this.usersList) return;
|
|
|
|
if (this.users.length === 0) {
|
|
this.usersList.innerHTML = `
|
|
<div class="admin-empty-state">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2a5 5 0 0 1 5 5v2a5 5 0 0 1-10 0V7a5 5 0 0 1 5-5zm-7 18a7 7 0 0 1 14 0" stroke="currentColor" stroke-width="2" fill="none"/></svg>
|
|
<p>Keine Benutzer vorhanden</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
this.usersList.innerHTML = this.users.map(user => this.renderUserCard(user)).join('');
|
|
|
|
// Bind edit buttons
|
|
this.usersList.querySelectorAll('.admin-user-card').forEach(card => {
|
|
const userId = parseInt(card.dataset.userId);
|
|
const editBtn = card.querySelector('.btn-edit-user');
|
|
editBtn?.addEventListener('click', () => this.openEditUserModal(userId));
|
|
});
|
|
}
|
|
|
|
renderUserCard(user) {
|
|
const initial = (user.display_name || user.username).charAt(0).toUpperCase();
|
|
const isLocked = user.locked_until && new Date(user.locked_until) > new Date();
|
|
const permissions = user.permissions || [];
|
|
|
|
return `
|
|
<div class="admin-user-card" data-user-id="${user.id}">
|
|
<div class="admin-user-avatar" style="background-color: ${user.color || '#808080'}">
|
|
${initial}
|
|
</div>
|
|
<div class="admin-user-info">
|
|
<div class="admin-user-name">${this.escapeHtml(user.display_name)}</div>
|
|
<div class="admin-user-username">@${this.escapeHtml(user.username)}${user.email ? ` · ${this.escapeHtml(user.email)}` : ''}</div>
|
|
</div>
|
|
<div class="admin-user-badges">
|
|
<span class="admin-badge role-${user.role || 'user'}">
|
|
${user.role === 'admin' ? 'Admin' : 'Benutzer'}
|
|
</span>
|
|
${permissions.map(p => `<span class="admin-badge permission">${this.escapeHtml(p)}</span>`).join('')}
|
|
</div>
|
|
<div class="admin-user-status">
|
|
${isLocked ? '<span class="status-locked">Gesperrt</span>' : '<span class="status-active">Aktiv</span>'}
|
|
</div>
|
|
<div class="admin-user-actions">
|
|
<button class="btn-edit-user" title="Bearbeiten">
|
|
<svg viewBox="0 0 24 24" width="16" height="16"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" fill="none"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" fill="none"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
generateRandomPassword(length = 10) {
|
|
const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
|
|
const lower = 'abcdefghjkmnpqrstuvwxyz';
|
|
const numbers = '23456789';
|
|
const special = '!@#$%&*?';
|
|
|
|
// Mindestens ein Zeichen aus jeder Kategorie
|
|
let password = '';
|
|
password += upper.charAt(Math.floor(Math.random() * upper.length));
|
|
password += lower.charAt(Math.floor(Math.random() * lower.length));
|
|
password += numbers.charAt(Math.floor(Math.random() * numbers.length));
|
|
password += special.charAt(Math.floor(Math.random() * special.length));
|
|
|
|
// Rest mit gemischten Zeichen auffüllen
|
|
const allChars = upper + lower + numbers + special;
|
|
for (let i = password.length; i < length; i++) {
|
|
password += allChars.charAt(Math.floor(Math.random() * allChars.length));
|
|
}
|
|
|
|
// Passwort mischen
|
|
return password.split('').sort(() => Math.random() - 0.5).join('');
|
|
}
|
|
|
|
openNewUserModal() {
|
|
this.currentEditUser = null;
|
|
this.userModalTitle.textContent = 'Neuer Benutzer';
|
|
this.userForm.reset();
|
|
this.editUserId.value = '';
|
|
|
|
// Passwort-Feld anzeigen (falls bei Bearbeitung versteckt)
|
|
this.passwordInput.closest('.form-group').style.display = '';
|
|
|
|
// Zufälliges Passwort generieren und anzeigen (10 Zeichen mit Sonderzeichen)
|
|
const randomPassword = this.generateRandomPassword(10);
|
|
this.passwordInput.value = randomPassword;
|
|
this.passwordInput.readOnly = true;
|
|
this.passwordInput.type = 'text';
|
|
this.passwordHint.textContent = '(automatisch generiert)';
|
|
|
|
this.usernameInput.disabled = false;
|
|
this.emailInput.disabled = false;
|
|
this.deleteUserBtn.classList.add('hidden');
|
|
this.unlockUserBtn.classList.add('hidden');
|
|
this.togglePermissionsVisibility();
|
|
this.openModal();
|
|
}
|
|
|
|
openEditUserModal(userId) {
|
|
const user = this.users.find(u => u.id === userId);
|
|
if (!user) return;
|
|
|
|
this.currentEditUser = user;
|
|
this.userModalTitle.textContent = 'Benutzer bearbeiten';
|
|
this.editUserId.value = user.id;
|
|
this.usernameInput.value = user.username;
|
|
this.usernameInput.disabled = true; // Username cannot be changed
|
|
this.displayNameInput.value = user.display_name;
|
|
this.emailInput.value = user.email || '';
|
|
this.emailInput.disabled = false;
|
|
|
|
// Passwort-Feld für Bearbeitung vorbereiten
|
|
this.passwordInput.closest('.form-group').style.display = 'block';
|
|
this.passwordInput.value = '';
|
|
this.passwordInput.placeholder = 'Neues Passwort (leer lassen = unverändert)';
|
|
this.passwordInput.readOnly = true;
|
|
this.passwordHint.textContent = '(optional - leer lassen für unverändert)';
|
|
|
|
this.roleSelect.value = user.role || 'user';
|
|
|
|
// Set permissions
|
|
const permissions = user.permissions || [];
|
|
this.permGenehmigung.checked = permissions.includes('genehmigung');
|
|
|
|
// Show/hide delete button (cannot delete self or last admin)
|
|
const canDelete = user.id !== authManager.getUser()?.id;
|
|
this.deleteUserBtn.classList.toggle('hidden', !canDelete);
|
|
|
|
// Show unlock button if user is locked
|
|
const isLocked = user.locked_until && new Date(user.locked_until) > new Date();
|
|
this.unlockUserBtn.classList.toggle('hidden', !isLocked);
|
|
|
|
this.togglePermissionsVisibility();
|
|
this.openModal();
|
|
}
|
|
|
|
togglePermissionsVisibility() {
|
|
const isAdmin = this.roleSelect.value === 'admin';
|
|
this.permissionsGroup.style.display = isAdmin ? 'none' : 'block';
|
|
}
|
|
|
|
async handleUserSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const userId = this.editUserId.value;
|
|
const isEdit = !!userId;
|
|
|
|
const data = {
|
|
displayName: this.displayNameInput.value.trim(),
|
|
email: this.emailInput.value.trim(),
|
|
role: this.roleSelect.value,
|
|
permissions: this.roleSelect.value === 'admin' ? [] : this.getSelectedPermissions()
|
|
};
|
|
|
|
if (!isEdit) {
|
|
data.username = this.usernameInput.value.trim().toUpperCase();
|
|
}
|
|
|
|
if (this.passwordInput.value) {
|
|
data.password = this.passwordInput.value;
|
|
}
|
|
|
|
try {
|
|
if (isEdit) {
|
|
await api.updateAdminUser(userId, data);
|
|
this.showToast('Benutzer aktualisiert', 'success');
|
|
} else {
|
|
await api.createAdminUser(data);
|
|
this.showToast('Benutzer erstellt', 'success');
|
|
}
|
|
|
|
this.closeModal();
|
|
await this.loadUsers();
|
|
} catch (error) {
|
|
this.showToast(error.message || 'Fehler beim Speichern', 'error');
|
|
}
|
|
}
|
|
|
|
async handleDeleteUser() {
|
|
if (!this.currentEditUser) return;
|
|
|
|
const confirmDelete = confirm(`Benutzer "${this.currentEditUser.display_name}" wirklich löschen?`);
|
|
if (!confirmDelete) return;
|
|
|
|
try {
|
|
await api.deleteAdminUser(this.currentEditUser.id);
|
|
this.showToast('Benutzer gelöscht', 'success');
|
|
this.closeModal();
|
|
await this.loadUsers();
|
|
} catch (error) {
|
|
this.showToast(error.message || 'Fehler beim Löschen', 'error');
|
|
}
|
|
}
|
|
|
|
async handleUnlockUser() {
|
|
if (!this.currentEditUser) return;
|
|
|
|
try {
|
|
await api.updateAdminUser(this.currentEditUser.id, { unlockAccount: true });
|
|
this.showToast('Benutzer entsperrt', 'success');
|
|
this.closeModal();
|
|
await this.loadUsers();
|
|
} catch (error) {
|
|
this.showToast(error.message || 'Fehler beim Entsperren', 'error');
|
|
}
|
|
}
|
|
|
|
getSelectedPermissions() {
|
|
const permissions = [];
|
|
if (this.permGenehmigung?.checked) {
|
|
permissions.push('genehmigung');
|
|
}
|
|
return permissions;
|
|
}
|
|
|
|
async handleLogout() {
|
|
try {
|
|
await authManager.logout();
|
|
window.location.reload();
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
}
|
|
}
|
|
|
|
openModal() {
|
|
if (this.userModal) {
|
|
this.userModal.classList.remove('hidden');
|
|
this.userModal.classList.add('visible');
|
|
}
|
|
const overlay = $('#modal-overlay');
|
|
if (overlay) {
|
|
overlay.classList.remove('hidden');
|
|
overlay.classList.add('visible');
|
|
}
|
|
store.openModal('user-modal');
|
|
}
|
|
|
|
closeModal() {
|
|
if (this.userModal) {
|
|
this.userModal.classList.remove('visible');
|
|
this.userModal.classList.add('hidden');
|
|
}
|
|
// Only hide overlay if no other modals are open
|
|
const openModals = store.get('openModals').filter(id => id !== 'user-modal');
|
|
if (openModals.length === 0) {
|
|
const overlay = $('#modal-overlay');
|
|
if (overlay) {
|
|
overlay.classList.remove('visible');
|
|
overlay.classList.add('hidden');
|
|
}
|
|
}
|
|
store.closeModal('user-modal');
|
|
}
|
|
|
|
showToast(message, type = 'info') {
|
|
window.dispatchEvent(new CustomEvent('toast:show', {
|
|
detail: { message, type }
|
|
}));
|
|
}
|
|
|
|
escapeHtml(str) {
|
|
if (!str) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = str;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// =====================
|
|
// UPLOAD SETTINGS
|
|
// =====================
|
|
|
|
async loadUploadSettings() {
|
|
try {
|
|
this.uploadSettings = await api.getUploadSettings();
|
|
this.allowedExtensions = this.uploadSettings.allowedExtensions || ['pdf', 'docx', 'txt'];
|
|
this.renderUploadSettings();
|
|
} catch (error) {
|
|
console.error('Error loading upload settings:', error);
|
|
}
|
|
}
|
|
|
|
renderUploadSettings() {
|
|
if (!this.uploadSettings) return;
|
|
|
|
// Maximale Dateigröße setzen
|
|
if (this.uploadMaxSizeInput) {
|
|
this.uploadMaxSizeInput.value = this.uploadSettings.maxFileSizeMB || 15;
|
|
}
|
|
|
|
// Extension-Tags rendern
|
|
this.renderExtensionTags();
|
|
|
|
// Vorschläge rendern
|
|
this.renderExtensionSuggestions();
|
|
}
|
|
|
|
renderExtensionTags() {
|
|
if (!this.extensionTagsContainer) return;
|
|
|
|
if (this.allowedExtensions.length === 0) {
|
|
this.extensionTagsContainer.innerHTML = '<span class="extension-empty">Keine Endungen definiert</span>';
|
|
return;
|
|
}
|
|
|
|
this.extensionTagsContainer.innerHTML = this.allowedExtensions.map(ext => `
|
|
<span class="extension-tag" data-extension="${ext}">
|
|
.${ext}
|
|
<button type="button" class="extension-tag-remove" data-remove="${ext}" title="Entfernen">
|
|
<svg viewBox="0 0 24 24" width="12" height="12"><path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
|
</button>
|
|
</span>
|
|
`).join('');
|
|
|
|
// Remove-Buttons Event Listener
|
|
this.extensionTagsContainer.querySelectorAll('.extension-tag-remove').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const ext = btn.dataset.remove;
|
|
this.removeExtension(ext);
|
|
});
|
|
});
|
|
}
|
|
|
|
renderExtensionSuggestions() {
|
|
if (!this.extensionSuggestionsList) return;
|
|
|
|
// Nur Vorschläge anzeigen, die noch nicht aktiv sind
|
|
const availableSuggestions = this.extensionSuggestions.filter(
|
|
ext => !this.allowedExtensions.includes(ext)
|
|
);
|
|
|
|
if (availableSuggestions.length === 0) {
|
|
this.extensionSuggestionsList.innerHTML = '<span class="extension-no-suggestions">Alle Vorschläge bereits hinzugefügt</span>';
|
|
return;
|
|
}
|
|
|
|
this.extensionSuggestionsList.innerHTML = availableSuggestions.map(ext => `
|
|
<button type="button" class="extension-suggestion" data-suggestion="${ext}">+ ${ext}</button>
|
|
`).join('');
|
|
|
|
// Suggestion-Buttons Event Listener
|
|
this.extensionSuggestionsList.querySelectorAll('.extension-suggestion').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const ext = btn.dataset.suggestion;
|
|
this.addExtension(ext);
|
|
});
|
|
});
|
|
}
|
|
|
|
addExtensionFromInput() {
|
|
const input = this.extensionInput?.value?.trim().toLowerCase();
|
|
if (!input) return;
|
|
|
|
// Punkt am Anfang entfernen falls vorhanden
|
|
const ext = input.replace(/^\./, '');
|
|
|
|
if (this.addExtension(ext)) {
|
|
this.extensionInput.value = '';
|
|
}
|
|
}
|
|
|
|
addExtension(ext) {
|
|
// Validierung: nur alphanumerisch, 1-10 Zeichen
|
|
if (!/^[a-z0-9]{1,10}$/.test(ext)) {
|
|
this.showToast('Ungültige Dateiendung (nur Buchstaben/Zahlen, max. 10 Zeichen)', 'error');
|
|
return false;
|
|
}
|
|
|
|
// Prüfen ob bereits vorhanden
|
|
if (this.allowedExtensions.includes(ext)) {
|
|
this.showToast(`Endung .${ext} bereits vorhanden`, 'error');
|
|
return false;
|
|
}
|
|
|
|
// Hinzufügen
|
|
this.allowedExtensions.push(ext);
|
|
this.renderExtensionTags();
|
|
this.renderExtensionSuggestions();
|
|
return true;
|
|
}
|
|
|
|
removeExtension(ext) {
|
|
// Prüfen ob mindestens eine Endung übrig bleibt
|
|
if (this.allowedExtensions.length <= 1) {
|
|
this.showToast('Mindestens eine Dateiendung muss erlaubt sein', 'error');
|
|
return;
|
|
}
|
|
|
|
this.allowedExtensions = this.allowedExtensions.filter(e => e !== ext);
|
|
this.renderExtensionTags();
|
|
this.renderExtensionSuggestions();
|
|
}
|
|
|
|
async saveUploadSettings() {
|
|
try {
|
|
const maxFileSizeMB = parseInt(this.uploadMaxSizeInput?.value) || 15;
|
|
|
|
// Validierung
|
|
if (maxFileSizeMB < 1 || maxFileSizeMB > 100) {
|
|
this.showToast('Dateigröße muss zwischen 1 und 100 MB liegen', 'error');
|
|
return;
|
|
}
|
|
|
|
if (this.allowedExtensions.length === 0) {
|
|
this.showToast('Mindestens eine Dateiendung muss erlaubt sein', 'error');
|
|
return;
|
|
}
|
|
|
|
await api.updateUploadSettings({
|
|
maxFileSizeMB,
|
|
allowedExtensions: this.allowedExtensions
|
|
});
|
|
|
|
this.uploadSettings = { maxFileSizeMB, allowedExtensions: this.allowedExtensions };
|
|
this.showToast('Upload-Einstellungen gespeichert', 'success');
|
|
} catch (error) {
|
|
console.error('Error saving upload settings:', error);
|
|
this.showToast(error.message || 'Fehler beim Speichern', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Passwort-Bearbeitung umschalten
|
|
*/
|
|
togglePasswordEdit() {
|
|
const passwordInput = $('#user-password');
|
|
const editBtn = $('#edit-password-btn');
|
|
const hint = $('#password-hint');
|
|
|
|
if (!passwordInput || !editBtn) return;
|
|
|
|
if (passwordInput.readOnly) {
|
|
// Bearbeitung aktivieren
|
|
passwordInput.readOnly = false;
|
|
passwordInput.focus();
|
|
passwordInput.select();
|
|
editBtn.innerHTML = `
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
<path d="M20 6L9 17l-5-5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
`;
|
|
editBtn.title = "Bearbeitung bestätigen";
|
|
hint.textContent = "(bearbeiten)";
|
|
} else {
|
|
// Bearbeitung beenden
|
|
passwordInput.readOnly = true;
|
|
editBtn.innerHTML = `
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
<path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
</svg>
|
|
`;
|
|
editBtn.title = "Passwort bearbeiten";
|
|
hint.textContent = this.currentEditUser ? "(geändert)" : "(automatisch generiert)";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Neues Passwort generieren
|
|
*/
|
|
generatePassword() {
|
|
const passwordInput = $('#user-password');
|
|
const hint = $('#password-hint');
|
|
|
|
if (!passwordInput) return;
|
|
|
|
// Starkes Passwort generieren (12 Zeichen)
|
|
const charset = 'ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789!@#$%&*';
|
|
let password = '';
|
|
for (let i = 0; i < 12; i++) {
|
|
password += charset.charAt(Math.floor(Math.random() * charset.length));
|
|
}
|
|
|
|
passwordInput.value = password;
|
|
passwordInput.readOnly = false;
|
|
|
|
if (hint) {
|
|
hint.textContent = "(neu generiert)";
|
|
}
|
|
|
|
// Passwort kurz markieren
|
|
passwordInput.focus();
|
|
passwordInput.select();
|
|
|
|
this.showToast('Neues Passwort generiert', 'success');
|
|
}
|
|
|
|
show() {
|
|
this.adminScreen?.classList.add('active');
|
|
}
|
|
|
|
hide() {
|
|
this.adminScreen?.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// Create singleton instance
|
|
const adminManager = new AdminManager();
|
|
|
|
export { adminManager };
|
|
export default adminManager;
|