(() => { const pluginList = document.querySelector('#plugin-list'); const adminLabelList = document.querySelector('#admin-label-list'); const userList = document.querySelector('#user-list'); const userForm = document.querySelector('#user-form'); const adminControls = document.querySelector('#admin-controls'); const adminAuthNotice = document.querySelector('#admin-auth-notice'); const accessToken = localStorage.getItem('linklogAccessToken'); function authHeaders(includeJson = false) { return { ...(includeJson ? {'Content-Type': 'application/json'} : {}), ...(accessToken ? {Authorization: `Bearer ${accessToken}`} : {}), }; } function renderPlugins(plugins) { pluginList.replaceChildren(...plugins.map((plugin) => { const row = document.createElement('div'); row.className = 'plugin-row'; const label = document.createElement('span'); label.textContent = `${plugin.name} (${plugin.version})`; const button = document.createElement('button'); button.type = 'button'; button.textContent = plugin.enabled ? 'Disable' : 'Enable'; button.addEventListener('click', () => updatePlugin(plugin, button)); row.append(label, button); return row; })); } function renderLabels(labels) { adminLabelList.replaceChildren(...labels.map((label) => { const row = document.createElement('div'); row.className = 'plugin-row'; const text = document.createElement('span'); text.textContent = `${label.name}${label.creator ? ` (${label.creator})` : ' (default)'}`; const button = document.createElement('button'); button.type = 'button'; button.className = 'danger-button'; button.textContent = 'Delete'; button.addEventListener('click', async () => { const response = await fetch(`/api/admin/labels/${label.id}`, {method: 'DELETE', headers: authHeaders()}); if (response.ok) loadLabels(); }); row.append(text, button); return row; })); } async function loadLabels() { const response = await fetch('/api/admin/labels', {headers: authHeaders()}); if (!response.ok) throw new Error('Could not load labels'); renderLabels(await response.json()); } function renderUsers(users) { userList.replaceChildren(...users.map((user) => { const row = document.createElement('div'); row.className = 'plugin-row'; const label = document.createElement('span'); label.textContent = `${user.username} (${user.email})${user.is_admin ? ' - admin' : ''}`; const privilegeLabel = document.createElement('label'); privilegeLabel.className = 'user-admin-toggle'; const privilegeCheckbox = document.createElement('input'); privilegeCheckbox.type = 'checkbox'; privilegeCheckbox.checked = user.is_admin; privilegeCheckbox.setAttribute('aria-label', `Administrator rights for ${user.username}`); privilegeCheckbox.addEventListener('change', () => updateUserPrivilege(user, privilegeCheckbox)); privilegeLabel.append(privilegeCheckbox, document.createTextNode(' Administrator')); const button = document.createElement('button'); button.type = 'button'; button.className = 'danger-button'; button.textContent = 'Remove'; button.addEventListener('click', () => removeUser(user, button)); row.append(label, privilegeLabel, button); return row; })); } async function loadUsers() { const response = await fetch('/api/admin/users', {headers: authHeaders()}); if (!response.ok) throw new Error('Could not load users'); renderUsers(await response.json()); } function showAdminState(isAdmin) { adminControls.classList.toggle('hidden', !isAdmin); adminAuthNotice.classList.toggle('hidden', isAdmin); } function showSignedOutState() { showAdminState(false); adminAuthNotice.innerHTML = 'Administrator sign-in required. Sign in'; } function showUnauthorizedState() { showAdminState(false); adminAuthNotice.textContent = 'You are signed in, but you are not authorized to access this page.'; } async function loadAdminState() { if (!accessToken) { showSignedOutState(); return; } const sessionResponse = await fetch(`/api/auth/me?token=${encodeURIComponent(accessToken)}`); if (!sessionResponse.ok) { localStorage.removeItem('linklogAccessToken'); showSignedOutState(); return; } const user = await sessionResponse.json(); if (!user.is_admin) { showUnauthorizedState(); return; } await Promise.all([loadUsers(), loadPlugins(), loadLabels()]); showAdminState(true); } async function loadPlugins() { const response = await fetch('/api/admin/plugins', {headers: authHeaders()}); if (!response.ok) throw new Error('Could not load plugins'); renderPlugins(await response.json()); } async function updatePlugin(plugin, button) { button.disabled = true; const response = await fetch(`/api/admin/plugins/${encodeURIComponent(plugin.name)}`, { method: 'PUT', headers: authHeaders(true), body: JSON.stringify({enabled: !plugin.enabled}), }); if (response.ok) { await loadPlugins(); } else { button.disabled = false; } } async function removeUser(user, button) { if (!window.confirm(`Remove ${user.username}?`)) return; button.disabled = true; const response = await fetch(`/api/admin/users/${encodeURIComponent(user.id)}`, { method: 'DELETE', headers: authHeaders(), }); if (response.ok) { await loadUsers(); } else { button.disabled = false; } } async function updateUserPrivilege(user, checkbox) { checkbox.disabled = true; const response = await fetch(`/api/admin/users/${encodeURIComponent(user.id)}`, { method: 'PUT', headers: authHeaders(true), body: JSON.stringify({is_admin: checkbox.checked}), }); if (response.ok) { await loadUsers(); } else { checkbox.checked = user.is_admin; checkbox.disabled = false; } } userForm.addEventListener('submit', async (event) => { event.preventDefault(); const values = Object.fromEntries(new FormData(userForm)); values.is_admin = userForm.elements.is_admin.checked; const response = await fetch('/api/admin/users', { method: 'POST', headers: authHeaders(true), body: JSON.stringify(values), }); const status = document.querySelector('#user-status'); status.textContent = response.ok ? 'User added.' : 'Could not add user.'; status.style.color = response.ok ? '#94e2d5' : '#f38ba8'; if (response.ok) { userForm.reset(); await loadUsers(); } }); loadAdminState().catch((error) => { showAdminState(false); adminAuthNotice.textContent = accessToken ? `Could not verify administrator access: ${error.message}` : 'Administrator sign-in required.'; adminAuthNotice.classList.remove('hidden'); userList.textContent = ''; pluginList.textContent = ''; adminLabelList.textContent = ''; }); })();