(() => { const normalizeUrl = (url) => { const next = new URL(url, window.location.origin); next.searchParams.set('partial', '1'); return next.toString(); }; const renderError = (host, message) => { host.innerHTML = `

${String(message || 'User Management konnte nicht geladen werden.')}

`; }; const escapeHtml = (value) => String(value ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); const bindUserPickers = (host) => { const dataNode = host.querySelector('#um-existing-users-data'); if (!(dataNode instanceof HTMLScriptElement)) { return; } let users = []; try { const decoded = JSON.parse(dataNode.textContent || '[]'); if (Array.isArray(decoded)) { users = decoded; } } catch (_error) { users = []; } const normalizedUsers = users.map((user) => ({ username: String(user?.username || ''), display_name: String(user?.display_name || ''), email: String(user?.email || ''), group_dns: Array.isArray(user?.group_dns) ? user.group_dns.map((group) => String(group || '')) : [], search: [ String(user?.username || ''), String(user?.display_name || ''), String(user?.email || ''), ].join(' ').toLowerCase(), })).filter((user) => user.username !== ''); const closeAll = () => { host.querySelectorAll('[data-user-picker-list]').forEach((list) => { list.setAttribute('hidden', 'hidden'); list.innerHTML = ''; }); }; host.querySelectorAll('[data-user-picker]').forEach((picker) => { const input = picker.querySelector('[data-user-picker-input]'); const list = picker.querySelector('[data-user-picker-list]'); if (!(input instanceof HTMLInputElement) || !(list instanceof HTMLElement)) { return; } if (input.dataset.umPickerBound === '1') { return; } input.dataset.umPickerBound = '1'; const syncGroupSelection = (username) => { const normalizedUsername = String(username || '').trim().toLowerCase(); host.querySelectorAll('input[name="manual_group_dns[]"]').forEach((checkbox) => { if (!(checkbox instanceof HTMLInputElement)) { return; } checkbox.checked = false; }); if (normalizedUsername === '') { return; } const user = normalizedUsers.find((entry) => entry.username.toLowerCase() === normalizedUsername); if (!user) { return; } const selectedGroups = new Set(user.group_dns); host.querySelectorAll('input[name="manual_group_dns[]"]').forEach((checkbox) => { if (!(checkbox instanceof HTMLInputElement)) { return; } checkbox.checked = selectedGroups.has(checkbox.value); }); }; const renderMatches = () => { const needle = input.value.trim().toLowerCase(); const matches = normalizedUsers .filter((user) => needle === '' || user.search.includes(needle)) .slice(0, needle === '' ? 80 : 25); list.innerHTML = ''; if (matches.length === 0) { list.innerHTML = '
Keine passenden Benutzer gefunden.
'; list.hidden = false; return; } list.innerHTML = matches.map((user) => ` `).join(''); list.hidden = false; }; input.addEventListener('focus', renderMatches); input.addEventListener('input', renderMatches); list.addEventListener('click', (event) => { const option = event.target.closest('[data-user-picker-value]'); if (!(option instanceof HTMLElement)) { return; } input.value = String(option.dataset.userPickerValue || ''); list.hidden = true; list.innerHTML = ''; syncGroupSelection(input.value); input.dispatchEvent(new Event('change', { bubbles: true })); }); input.addEventListener('change', () => { syncGroupSelection(input.value); }); if (input.value.trim() !== '') { syncGroupSelection(input.value); } }); if (!host.dataset.umPickerOutsideBound) { host.dataset.umPickerOutsideBound = '1'; document.addEventListener('click', (event) => { if (!(event.target instanceof Node) || !host.contains(event.target)) { closeAll(); return; } const insidePicker = event.target instanceof Element && event.target.closest('[data-user-picker]'); if (!insidePicker) { closeAll(); } }); } }; const bindInteractions = (host, loadRoute) => { host.querySelectorAll('a[href]').forEach((link) => { if (link.dataset.umBound === '1') { return; } const href = String(link.getAttribute('href') || ''); if (!href.startsWith('/admin/users/')) { return; } link.dataset.umBound = '1'; link.addEventListener('click', (event) => { event.preventDefault(); loadRoute(href); }); }); host.querySelectorAll('form[action]').forEach((form) => { if (form.dataset.umBound === '1') { return; } const action = String(form.getAttribute('action') || ''); if (!action.startsWith('/admin/users/')) { return; } form.dataset.umBound = '1'; form.addEventListener('submit', async (event) => { event.preventDefault(); try { host.classList.add('is-loading'); const response = await fetch(normalizeUrl(action), { method: 'POST', credentials: 'same-origin', body: new FormData(form), }); const html = await response.text(); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } host.innerHTML = html; bindInteractions(host, loadRoute); bindUserPickers(host); } catch (error) { renderError(host, error?.message || 'Formular konnte nicht verarbeitet werden.'); } finally { host.classList.remove('is-loading'); } }); }); }; window.initUserManagementApp = function initUserManagementApp(host, options = {}) { if (!(host instanceof Element)) { return; } if (host.dataset.umAppBound === '1') { return; } host.dataset.umAppBound = '1'; const entryRoute = String(options.entryRoute || '/admin/users/'); const loadRoute = async (route) => { try { host.classList.add('is-loading'); const response = await fetch(normalizeUrl(route), { credentials: 'same-origin', headers: { Accept: 'text/html' }, }); const html = await response.text(); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } host.innerHTML = html; bindInteractions(host, loadRoute); bindUserPickers(host); } catch (error) { renderError(host, error?.message || 'User Management konnte nicht geladen werden.'); } finally { host.classList.remove('is-loading'); } }; if (options.standalone) { bindInteractions(host, loadRoute); bindUserPickers(host); return; } loadRoute(entryRoute); }; })();