Datenbank bereinigt / Gitea-Integration gefixt

Dieser Commit ist enthalten in:
hendrik_gebhardt@gmx.de
2026-01-04 00:24:11 +00:00
committet von Server Deploy
Ursprung 395598c2b0
Commit c21be47428
37 geänderte Dateien mit 30993 neuen und 809 gelöschten Zeilen

Datei anzeigen

@ -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');
}