/** * Utility functions for domain consensus plugin */ /** * Format a timestamp to human-readable string */ function formatTimestamp(timestamp) { if (!timestamp) return 'Never'; const date = new Date(timestamp); const now = new Date(); const diff = now - date; if (diff < 60000) { return `${Math.floor(diff / 1000)}s ago`; } else if (diff < 3600000) { return `${Math.floor(diff / 60000)}m ago`; } else if (diff < 86400000) { return `${Math.floor(diff / 3600000)}h ago`; } else { return date.toLocaleString(); } } /** * Format a peer ID to shortened version */ function formatPeerId(peerId) { if (!peerId) return 'N/A'; if (peerId.length <= 16) return peerId; return `${peerId.slice(0, 8)}...${peerId.slice(-8)}`; } /** * Format a hash to shortened version */ function formatHash(hash) { if (!hash) return 'N/A'; if (hash.length <= 20) return hash; return `${hash.slice(0, 10)}...${hash.slice(-10)}`; } /** * Get status badge class based on consensus status */ function getStatusBadgeClass(status) { switch (status) { case 'resolved': return 'status-badge resolved'; case 'insufficient_quorum': return 'status-badge insufficient_quorum'; case 'tie': return 'status-badge tie'; case 'no_claims': return 'status-badge no_claims'; case 'error': return 'status-badge error'; default: return 'status-badge no_claims'; } } /** * Get status text for consensus status */ function getStatusText(status) { switch (status) { case 'resolved': return 'Resolved'; case 'insufficient_quorum': return 'Insufficient Quorum'; case 'tie': return 'Tie'; case 'no_claims': return 'No Claims'; case 'error': return 'Error'; default: return 'Unknown'; } } /** * Get status color for consensus status */ function getStatusColor(status) { switch (status) { case 'resolved': return '#10b981'; // green-500 case 'insufficient_quorum': return '#eab308'; // yellow-500 case 'tie': return '#f97316'; // orange-500 case 'no_claims': return '#6b7280'; // gray-500 case 'error': return '#ef4444'; // red-500 default: return '#6b7280'; // gray-500 } } /** * Calculate quorum percentage */ function calculateQuorumPercentage(totalVotes, minVotes) { if (minVotes === 0) return 0; return Math.min(100, Math.round((totalVotes / minVotes) * 100)); } /** * Escape HTML to prevent XSS */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Copy text to clipboard */ async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); return true; } catch (err) { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.opacity = '0'; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); document.body.removeChild(textArea); return true; } catch (err) { document.body.removeChild(textArea); return false; } } } /** * Debounce function */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } /** * Format number with commas */ function formatNumber(num) { if (num === null || num === undefined) return '0'; return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } /** * Create a progress bar element */ function createProgressBar(percentage, color = '#3b82f6') { const bar = document.createElement('div'); bar.className = 'w-full progress-bar-container'; bar.innerHTML = `
`; return bar; } /** * Update sidebar stats (can be called from any view) */ async function updateSidebarStats() { try { // Fetch overview and peers data to get stats const [overviewData, peersData] = await Promise.all([ window.apiClient.getOverview(), window.apiClient.getPeers() ]); const stats = overviewData.stats || {}; const activePeers = peersData.activePeers || 0; const totalDomainsEl = document.getElementById('statTotalDomains'); const resolvedEl = document.getElementById('statResolved'); const activePeersEl = document.getElementById('statActivePeers'); if (totalDomainsEl) totalDomainsEl.textContent = formatNumber(stats.totalDomains || 0); if (resolvedEl) resolvedEl.textContent = formatNumber(stats.resolved || 0); if (activePeersEl) activePeersEl.textContent = formatNumber(activePeers); } catch (error) { console.error('Error updating sidebar stats:', error); // Set to 0 on error const totalDomainsEl = document.getElementById('statTotalDomains'); const resolvedEl = document.getElementById('statResolved'); const activePeersEl = document.getElementById('statActivePeers'); if (totalDomainsEl) totalDomainsEl.textContent = '0'; if (resolvedEl) resolvedEl.textContent = '0'; if (activePeersEl) activePeersEl.textContent = '0'; } } /** * Export functions */ window.utils = { formatTimestamp, formatPeerId, formatHash, getStatusBadgeClass, getStatusText, getStatusColor, calculateQuorumPercentage, escapeHtml, copyToClipboard, debounce, formatNumber, createProgressBar, updateSidebarStats };