Datenbank bereinigt / Gitea-Integration gefixt
Dieser Commit ist enthalten in:
committet von
Server Deploy
Ursprung
395598c2b0
Commit
c21be47428
@ -14,7 +14,17 @@ class AdminManager {
|
||||
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() {
|
||||
@ -52,7 +62,10 @@ class AdminManager {
|
||||
// Upload Settings Elements
|
||||
this.uploadMaxSizeInput = $('#upload-max-size');
|
||||
this.saveUploadSettingsBtn = $('#btn-save-upload-settings');
|
||||
this.uploadCategories = $$('.upload-category');
|
||||
this.extensionTagsContainer = $('#extension-tags');
|
||||
this.extensionInput = $('#extension-input');
|
||||
this.addExtensionBtn = $('#btn-add-extension');
|
||||
this.extensionSuggestionsList = $('#extension-suggestions-list');
|
||||
|
||||
this.bindEvents();
|
||||
this.initialized = true;
|
||||
@ -88,13 +101,20 @@ class AdminManager {
|
||||
// Upload Settings - Save Button
|
||||
this.saveUploadSettingsBtn?.addEventListener('click', () => this.saveUploadSettings());
|
||||
|
||||
// Upload Settings - Category Toggles
|
||||
this.uploadCategories?.forEach(category => {
|
||||
const checkbox = category.querySelector('input[type="checkbox"]');
|
||||
checkbox?.addEventListener('change', () => {
|
||||
this.toggleUploadCategory(category, checkbox.checked);
|
||||
});
|
||||
// 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() {
|
||||
@ -222,8 +242,12 @@ class AdminManager {
|
||||
this.emailInput.value = user.email || '';
|
||||
this.emailInput.disabled = false;
|
||||
|
||||
// Passwort-Feld bei Bearbeitung ausblenden
|
||||
this.passwordInput.closest('.form-group').style.display = 'none';
|
||||
// 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';
|
||||
|
||||
@ -381,6 +405,7 @@ class AdminManager {
|
||||
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);
|
||||
@ -395,36 +420,108 @@ class AdminManager {
|
||||
this.uploadMaxSizeInput.value = this.uploadSettings.maxFileSizeMB || 15;
|
||||
}
|
||||
|
||||
// Kategorien setzen
|
||||
const categoryMap = {
|
||||
'images': 'upload-cat-images',
|
||||
'documents': 'upload-cat-documents',
|
||||
'office': 'upload-cat-office',
|
||||
'text': 'upload-cat-text',
|
||||
'archives': 'upload-cat-archives',
|
||||
'data': 'upload-cat-data'
|
||||
};
|
||||
// Extension-Tags rendern
|
||||
this.renderExtensionTags();
|
||||
|
||||
Object.entries(categoryMap).forEach(([category, checkboxId]) => {
|
||||
const checkbox = $(`#${checkboxId}`);
|
||||
const categoryEl = $(`.upload-category[data-category="${category}"]`);
|
||||
// Vorschläge rendern
|
||||
this.renderExtensionSuggestions();
|
||||
}
|
||||
|
||||
if (checkbox && this.uploadSettings.allowedTypes?.[category]) {
|
||||
const isEnabled = this.uploadSettings.allowedTypes[category].enabled;
|
||||
checkbox.checked = isEnabled;
|
||||
this.toggleUploadCategory(categoryEl, isEnabled);
|
||||
}
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
toggleUploadCategory(categoryEl, enabled) {
|
||||
if (!categoryEl) return;
|
||||
renderExtensionSuggestions() {
|
||||
if (!this.extensionSuggestionsList) return;
|
||||
|
||||
if (enabled) {
|
||||
categoryEl.classList.remove('disabled');
|
||||
} else {
|
||||
categoryEl.classList.add('disabled');
|
||||
// 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() {
|
||||
@ -437,51 +534,17 @@ class AdminManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Kategorien sammeln
|
||||
const allowedTypes = {
|
||||
images: {
|
||||
enabled: $('#upload-cat-images')?.checked ?? true,
|
||||
types: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
|
||||
},
|
||||
documents: {
|
||||
enabled: $('#upload-cat-documents')?.checked ?? true,
|
||||
types: ['application/pdf']
|
||||
},
|
||||
office: {
|
||||
enabled: $('#upload-cat-office')?.checked ?? true,
|
||||
types: [
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||
]
|
||||
},
|
||||
text: {
|
||||
enabled: $('#upload-cat-text')?.checked ?? true,
|
||||
types: ['text/plain', 'text/csv', 'text/markdown']
|
||||
},
|
||||
archives: {
|
||||
enabled: $('#upload-cat-archives')?.checked ?? true,
|
||||
types: ['application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed']
|
||||
},
|
||||
data: {
|
||||
enabled: $('#upload-cat-data')?.checked ?? true,
|
||||
types: ['application/json']
|
||||
}
|
||||
};
|
||||
|
||||
// Prüfen ob mindestens eine Kategorie aktiviert ist
|
||||
const hasEnabledCategory = Object.values(allowedTypes).some(cat => cat.enabled);
|
||||
if (!hasEnabledCategory) {
|
||||
this.showToast('Mindestens eine Dateikategorie muss aktiviert sein', 'error');
|
||||
if (this.allowedExtensions.length === 0) {
|
||||
this.showToast('Mindestens eine Dateiendung muss erlaubt sein', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
await api.updateUploadSettings({ maxFileSizeMB, allowedTypes });
|
||||
await api.updateUploadSettings({
|
||||
maxFileSizeMB,
|
||||
allowedExtensions: this.allowedExtensions
|
||||
});
|
||||
|
||||
this.uploadSettings = { maxFileSizeMB, allowedTypes };
|
||||
this.uploadSettings = { maxFileSizeMB, allowedExtensions: this.allowedExtensions };
|
||||
this.showToast('Upload-Einstellungen gespeichert', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error saving upload settings:', error);
|
||||
@ -489,6 +552,72 @@ class AdminManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren