// Configuration constants window.updateMap = { 'update-database': ['domains', 'entries'], 'update-peers': ['peers'], 'update-certs': ['certs'], 'update-interfaces': ['interfaces'], 'update-local-dns': ['local-dns', 'dns-conflicts'], 'update-holesail': [], 'update-holesail-clients': [], 'update-settings': ['settings'], 'update-stats': [], 'system-reset': () => location.reload() }; window.paginationState = { domains: { current: 1, size: 8 }, entries: { current: 1, size: 10 }, peers: { current: 1, size: 20 }, certs: { current: 1, size: 5 }, interfaces: { current: 1, size: 10 }, 'local-dns': { current: 1, size: 10 }, 'dns-conflicts': { current: 1, size: 10 }, 'host-servers': { current: 1, size: 10 }, 'host-clients': { current: 1, size: 10 }, plugins: { current: 1, size: 10 } }; // Helper function to get consensus status badge function getConsensusStatusBadge(status) { const badges = { 'resolved': 'Resolved', 'tie': 'Tie', 'insufficient_quorum': 'No Quorum', 'no_claims': 'No Claims', 'error': 'Error', 'internal': 'Internal', 'unknown': 'Unknown' }; return badges[status] || badges['unknown']; } // Make function globally available window.getConsensusStatusBadge = getConsensusStatusBadge; window.chartColors = { primary: 'rgb(59, 130, 246)', success: 'rgb(34, 197, 94)', warning: 'rgb(234, 179, 8)', danger: 'rgb(239, 68, 68)', info: 'rgb(59, 130, 246)', gray: 'rgb(107, 114, 128)', dark: 'rgb(17, 24, 39)' }; window.darkModeColors = { primary: 'rgb(96, 165, 250)', success: 'rgb(74, 222, 128)', warning: 'rgb(250, 204, 21)', danger: 'rgb(248, 113, 113)', info: 'rgb(96, 165, 250)', gray: 'rgb(156, 163, 175)', dark: 'rgb(243, 244, 246)' }; // Tabs configuration - uses functions from utils.js and other modules window.tabs = { domains: { api: '/api/resolved-domains', searchId: 'search-domains', dataKey: 'domainsData', filteredKey: 'filteredDomains', containerId: 'domainsTable', paginationId: 'domainsPagination', sort: (a, b) => a.domain.localeCompare(b.domain, undefined, { sensitivity: 'base' }), filter: (item, query) => item.domain.toLowerCase().includes(query) || item.hash.toLowerCase().includes(query), renderItem: (item) => { const tr = document.createElement('tr'); tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700'; let consensusInfo = ''; if (item.consensusState) { const statusBadge = getConsensusStatusBadge(item.consensusStatus); consensusInfo = `${statusBadge}`; } else if (item.consensusStatus === 'internal') { consensusInfo = `${getConsensusStatusBadge('internal')}`; } else { consensusInfo = `${getConsensusStatusBadge('unknown')}`; } tr.innerHTML = `${item.domain}${item.isLocal ? '🏠' : ''} ${item.hash} ${consensusInfo} ${item.isLocal && item.hash !== 'internal' ? `` : ''}`; return tr; }, postFetch: (data) => { return data.map(item => { if (item.consensusStatus === 'internal' || item.hash === 'internal' || item.hash === 'none') { return { ...item, consensusStatus: 'internal' }; } return { ...item, consensusStatus: item.consensusState?.status || 'unknown' }; }); } }, entries: { api: '/api/entries', searchId: 'search-entries', dataKey: 'entriesData', filteredKey: 'filteredEntries', containerId: 'entriesTable', paginationId: 'entriesPagination', sort: (a, b) => a.key.localeCompare(b.key, undefined, { sensitivity: 'base' }), filter: (item, query) => item.key.toLowerCase().includes(query) || item.value.toLowerCase().includes(query), renderItem: (item) => { const tr = document.createElement('tr'); tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700'; tr.innerHTML = `${item.key} ${item.value}`; return tr; } }, peers: { api: '/api/peers', searchId: 'search-peers', dataKey: 'peersData', filteredKey: 'filteredPeers', containerId: 'peersList', paginationId: 'peersPagination', sort: (a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }), filter: (item, query) => item.toLowerCase().includes(query), renderItem: (peer) => { const li = document.createElement('li'); li.className = 'p-4 bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow break-all'; li.textContent = peer; return li; }, preRender: (total) => { const el = document.getElementById('peers-count'); if (el) el.textContent = `(${total})`; } }, certs: { api: '/api/certs', searchId: 'search-certs', dataKey: 'certsData', filteredKey: 'filteredCerts', containerId: 'certsList', paginationId: 'certsPagination', sort: (a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }), filter: (item, query) => item.toLowerCase().includes(query), renderItem: (cert) => { const li = document.createElement('li'); li.className = 'p-4 bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow flex justify-between items-center'; li.innerHTML = `${cert}
`; return li; } }, interfaces: { api: '/api/interfaces', searchId: 'search-interfaces', dataKey: 'interfacesData', filteredKey: 'filteredInterfaces', containerId: 'interfacesTable', paginationId: 'interfacesPagination', sort: (a, b) => a.domain.localeCompare(b.domain, undefined, { sensitivity: 'base' }), filter: (item, query) => item.domain.toLowerCase().includes(query) || item.ip.toLowerCase().includes(query), renderItem: (item) => { const tr = document.createElement('tr'); tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700'; tr.innerHTML = `${item.domain} ${item.ip}`; return tr; } }, 'local-dns': { api: '/api/local-dns', searchId: 'search-local-dns', dataKey: 'localDnsData', filteredKey: 'filteredLocalDns', containerId: 'localDnsTable', paginationId: 'localDnsPagination', sort: (a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }), filter: (item, query) => { const queryLower = query.toLowerCase(); return ( item.name.toLowerCase().includes(queryLower) || item.type.toLowerCase().includes(queryLower) || Object.values(item).some(val => typeof val === 'string' && val.toLowerCase().includes(queryLower)) ); }, renderItem: (item) => { let valueStr = ''; if (item.type === 'MX') { valueStr = `${item.preference || ''} ${item.exchange || ''}`.trim(); } else if (item.type === 'SRV') { valueStr = `${item.priority || ''} ${item.weight || ''} ${item.port || ''} ${item.target || ''}`.trim(); } else if (item.type === 'SOA') { valueStr = `${item.mname || ''} ${item.rname || ''} ${item.serial || ''} ${item.refresh || ''} ${item.retry || ''} ${item.expire || ''} ${item.minimum || ''}`.trim(); } else if (item.type === 'CAA') { valueStr = `${item.flags || ''} ${item.tag || ''} ${item.value || ''}`.trim(); } else { valueStr = item.data || item.value || ''; } const tr = document.createElement('tr'); tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700'; tr.innerHTML = ` ${item.name} ${item.type} ${valueStr} ${item.ttl} `; return tr; }, postFetch: (data) => { window.dnsConflictsData = data.conflicts || []; window.filteredDnsConflicts = window.dnsConflictsData; data.records = data.records.map((rec, index) => ({ ...rec, index })); if (window.renderDnsConflicts) window.renderDnsConflicts(); return data.records; } }, 'dns-conflicts': { api: '/api/local-dns', searchId: 'search-dns-conflicts', dataKey: 'dnsConflictsData', filteredKey: 'filteredDnsConflicts', containerId: 'dnsConflictsTable', paginationId: 'dnsConflictsPagination', sort: (a, b) => a.domain.localeCompare(b.domain, undefined, { sensitivity: 'base' }), filter: (item, query) => item.domain.toLowerCase().includes(query) || item.version.toLowerCase().includes(query) || item.publicIP.toLowerCase().includes(query), renderItem: (item) => { const tr = document.createElement('tr'); tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700'; tr.innerHTML = ` ${item.domain} ${item.publicIP} `; return tr; }, postFetch: (data) => { window.localDnsData = data.records.map((rec, index) => ({ ...rec, index })); window.filteredLocalDns = window.localDnsData; return data.conflicts || []; } }, 'host-servers': { api: '/api/holesail-servers', searchId: 'search-holesail', dataKey: 'holesailServersData', filteredKey: 'filteredHolesailServers', containerId: 'holesailTable', paginationId: 'holesailPagination', sort: (a, b) => (a.opts.name || a.id).localeCompare(b.opts.name || b.id, undefined, { sensitivity: 'base' }), filter: (item, query) => (item.opts.name || '').toLowerCase().includes(query) || item.id.toLowerCase().includes(query) || item.opts.port.toString().includes(query) || (item.info.url || '').toLowerCase().includes(query), renderItem: (item) => { const isPendingRestart = window.pendingServerRestarts && window.pendingServerRestarts.has(item.id); const isPendingDelete = window.pendingServerDeletions && window.pendingServerDeletions.has(item.id); const restartBtn = isPendingRestart ? `` : ``; const deleteBtn = isPendingDelete ? `` : ``; const protocol = item.opts.udp ? 'UDP' : 'TCP'; const url = item.info.url || 'N/A'; const truncatedUrl = window.truncateUrl ? window.truncateUrl(url, 40) : url.length > 40 ? url.substring(0, 37) + '...' : url; const tr = document.createElement('tr'); tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700'; tr.innerHTML = ` ${item.opts.name || item.id} ${item.opts.port} ${item.opts.host || '0.0.0.0'} ${truncatedUrl} ${protocol} ${window.renderStatusBadge ? window.renderStatusBadge(item.info.state) : item.info.state} ${restartBtn} ${deleteBtn} `; return tr; } }, 'host-clients': { api: '/api/holesail-clients', searchId: 'search-holesail-clients', dataKey: 'holesailClientsData', filteredKey: 'filteredHolesailClients', containerId: 'holesailClientsTable', paginationId: 'holesailClientsPagination', sort: (a, b) => a.opts.domain.localeCompare(b.opts.domain, undefined, { sensitivity: 'base' }), filter: (item, query) => item.opts.domain.toLowerCase().includes(query) || item.opts.key.toLowerCase().includes(query) || item.opts.port.toString().includes(query), renderItem: (item) => { const isPendingRestart = window.pendingClientRestarts && window.pendingClientRestarts.has(item.id); const isPendingDelete = window.pendingClientDeletions && window.pendingClientDeletions.has(item.id); const restartBtn = isPendingRestart ? `` : ``; const deleteBtn = isPendingDelete ? `` : ``; const protocol = (item.opts.protocol || 'tcp').toUpperCase(); const key = item.opts.key || ''; const truncatedKey = window.truncateUrl ? window.truncateUrl(key, 30) : key.length > 30 ? key.substring(0, 27) + '...' : key; const tr = document.createElement('tr'); tr.className = 'border-b hover:bg-gray-50 dark:hover:bg-gray-700'; tr.innerHTML = ` ${item.opts.domain} ${truncatedKey} ${item.opts.port} ${protocol} ${window.renderStatusBadge ? window.renderStatusBadge(item.info.state) : item.info.state} ${restartBtn} ${deleteBtn} `; return tr; } }, settings: { api: '/api/settings', searchId: 'search-settings', dataKey: 'settingsData', filteredKey: 'filteredSettings', containerId: 'settingsContainer', paginationId: 'settingsPagination', sort: null, filter: null, renderItem: null, postFetch: (data) => { window.settingsMetadata = data.metadata || {}; return data.settings || {}; } } };