(function () { 'use strict'; async function init() { document.getElementById('loading').classList.remove('hidden'); try { await Util.fetchCsrfToken(); const [config] = await Promise.all([ fetch('/api/config', { credentials: 'same-origin' }).then(r => r.json()), DiscordFields.fetchGuildData() ]); Fields.setSavedConfig(config); document.getElementById('bot-status-dot').className = 'dot online'; document.getElementById('bot-status-text').textContent = 'Connected'; Fields.populateFields(config); Notifications.initNotificationsEditor(config); Fields.initSmartSelects(config); } catch (e) { document.getElementById('bot-status-dot').className = 'dot offline'; document.getElementById('bot-status-text').textContent = 'Unreachable'; } document.getElementById('loading').classList.add('hidden'); setupSectionToggles(); Fields.setupSaveBar(); } function setupSectionToggles() { document.querySelectorAll('.section-header').forEach(header => { header.addEventListener('click', () => { header.closest('.section').classList.toggle('collapsed'); }); }); } function openScheduleModal() { const modal = document.getElementById('schedule-modal'); modal.classList.remove('hidden'); const dt = document.getElementById('schedule-datetime'); const min = Util.formatLocalDateTime(new Date(Date.now() + 60000)); dt.min = min; dt.value = min; } async function confirmScheduledRestart() { const dt = document.getElementById('schedule-datetime').value; if (!dt) return; await fetch('/api/restart', { method: 'POST', credentials: 'same-origin', headers: Util.csrfHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ mode: 'scheduled', scheduledFor: new Date(dt).toISOString() }) }); document.getElementById('schedule-modal').classList.add('hidden'); Util.showToast(`Restart scheduled for ${new Date(dt).toLocaleString()}`, 'warning'); } async function doLogout() { try { await fetch('/logout', { method: 'POST', credentials: 'same-origin', headers: Util.csrfHeaders() }); } catch (e) { /* ignore */ } window.location.href = '/login'; } function setupActionButtons() { document.getElementById('save-btn')?.addEventListener('click', () => Fields.saveConfig('save')); document.getElementById('save-restart-btn')?.addEventListener('click', () => Fields.saveConfig('restart')); document.getElementById('schedule-restart-btn')?.addEventListener('click', openScheduleModal); document.getElementById('schedule-confirm-btn')?.addEventListener('click', confirmScheduledRestart); document.getElementById('schedule-cancel-btn')?.addEventListener('click', () => { document.getElementById('schedule-modal').classList.add('hidden'); }); document.getElementById('logout-btn')?.addEventListener('click', doLogout); } function setupMobileNav() { const toggle = document.getElementById('menu-toggle'); const backdrop = document.getElementById('sidebar-backdrop'); toggle?.addEventListener('click', () => { Util.setSidebarOpen(!document.body.classList.contains('sidebar-open')); }); backdrop?.addEventListener('click', () => Util.setSidebarOpen(false)); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && document.body.classList.contains('sidebar-open')) { Util.setSidebarOpen(false); } }); window.addEventListener('resize', () => { if (!Util.isMobileViewport() && document.body.classList.contains('sidebar-open')) { Util.setSidebarOpen(false); } }); } let healthPollHandle = null; function setBotStatus(online) { const dot = document.getElementById('bot-status-dot'); const text = document.getElementById('bot-status-text'); if (!dot || !text) return; dot.className = online ? 'dot online' : 'dot offline'; text.textContent = online ? 'Connected' : 'Unreachable'; } async function pollHealth() { try { const res = await fetch('/healthz', { credentials: 'same-origin' }); if (res.ok) { const data = await res.json(); setBotStatus(Boolean(data.bot)); } else { setBotStatus(false); } } catch (_) { setBotStatus(false); } } function scheduleNextHealthPoll() { if (document.hidden) return; healthPollHandle = setTimeout(async () => { await pollHealth(); scheduleNextHealthPoll(); }, 20000); } function startHealthPolling() { if (healthPollHandle) clearTimeout(healthPollHandle); scheduleNextHealthPoll(); } function stopHealthPolling() { if (healthPollHandle) { clearTimeout(healthPollHandle); healthPollHandle = null; } } function setupHealthPolling() { document.addEventListener('visibilitychange', () => { if (document.hidden) stopHealthPolling(); else startHealthPolling(); }); window.addEventListener('pagehide', stopHealthPolling); startHealthPolling(); } document.addEventListener('DOMContentLoaded', async () => { Router.setupSidebarRouting(); setupActionButtons(); setupMobileNav(); await init(); Router.navigate(location.pathname, false); setupHealthPolling(); }); window.App = { init }; })();