/** * P2NS Admin Panel - Utilities * Consolidated utilities matching the P2NS Plugin SDK */ // Initialize sdk global if not present window.sdk = window.sdk || {}; window.sdk.utils = window.sdk.utils || {}; /** * Formatting Utilities */ window.sdk.utils.format = { /** * Format a timestamp to human-readable relative time */ 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 */ formatPeerId(peerId, short = true) { if (!peerId) return 'N/A'; if (!short) return peerId; if (peerId.length <= 16) return peerId; return `${peerId.slice(0, 8)}...${peerId.slice(-8)}`; }, /** * Format a hash to shortened version */ formatHash(hash) { if (!hash) return 'N/A'; if (hash.length <= 20) return hash; return `${hash.slice(0, 10)}...${hash.slice(-10)}`; }, /** * Format duration (milliseconds to readable string) */ formatDuration(ms) { if (!ms || ms === 0) return '-'; if (ms < 1000) return `${Math.round(ms)}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`; if (ms < 3600000) return `${(ms / 60000).toFixed(2)}m`; return `${(ms / 3600000).toFixed(2)}h`; }, /** * Format uptime (milliseconds to readable string) */ formatUptime(ms) { if (!ms || ms === 0) return '0s'; const days = Math.floor(ms / 86400000); const hours = Math.floor((ms % 86400000) / 3600000); const minutes = Math.floor((ms % 3600000) / 60000); const seconds = Math.floor((ms % 60000) / 1000); if (days > 0) return `${days}d ${hours}h ${minutes}m`; if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`; if (minutes > 0) return `${minutes}m ${seconds}s`; return `${seconds}s`; }, /** * Format bytes to human readable string */ formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; if (!bytes) return 'N/A'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; }, /** * Format number with commas */ formatNumber(num) { if (num === null || num === undefined) return '0'; return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); }, /** * Format CPU usage */ formatCPUUsage(cpuUsage, uptime) { if (!cpuUsage) return 'N/A'; if (typeof cpuUsage.percentage === 'number') { return `${cpuUsage.percentage.toFixed(2)}%`; } if (typeof cpuUsage.user === 'number' && typeof cpuUsage.system === 'number') { if (uptime && uptime > 0) { const uptimeMicroseconds = uptime * 1000; const totalCpuMicroseconds = cpuUsage.user + cpuUsage.system; const cpuPercent = (totalCpuMicroseconds / uptimeMicroseconds) * 100; return `${cpuPercent.toFixed(2)}%`; } return `${(cpuUsage.user / 1000).toFixed(2)}ms user, ${(cpuUsage.system / 1000).toFixed(2)}ms system`; } return 'N/A'; }, /** * Format memory usage */ formatMemoryUsage(memoryUsage) { if (!memoryUsage) return 'N/A'; const rssMB = (memoryUsage.rss / 1024 / 1024).toFixed(2); const heapUsedMB = (memoryUsage.heapUsed / 1024 / 1024).toFixed(2); const heapTotalMB = (memoryUsage.heapTotal / 1024 / 1024).toFixed(2); return `${rssMB} MB RSS (${heapUsedMB}/${heapTotalMB} MB heap)`; }, /** * Format Holesail hash for display */ formatHolesailHash(hash) { if (!hash) return 'none'; if (hash.startsWith('hs://')) return hash; return `hs://${hash}`; } }; /** * DOM and UI Utilities */ window.sdk.utils.dom = { escapeHtml(text) { if (typeof text !== 'string') return text; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, async copyToClipboard(text) { try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(text); return true; } throw new Error('Clipboard API not available'); } catch (err) { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.opacity = '0'; document.body.appendChild(textArea); textArea.select(); const successful = document.execCommand('copy'); document.body.removeChild(textArea); return successful; } }, truncate(text, maxLength = 40) { if (!text || text === 'N/A') return 'N/A'; if (text.length <= maxLength) return text; return text.substring(0, maxLength - 3) + '...'; }, debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } }; /** * Status and Badge Utilities */ window.sdk.utils.status = { renderStatusBadge(state) { const badges = { 'running': { class: 'bg-green-500', icon: '✓', text: 'Running' }, 'stopped': { class: 'bg-gray-500', icon: '○', text: 'Stopped' }, 'starting': { class: 'bg-yellow-500', icon: '⟳', text: 'Starting' }, 'error': { class: 'bg-red-500', icon: '✗', text: 'Error' } }; const badge = badges[state] || badges['stopped']; return ` ${badge.icon} ${badge.text} `; }, getConsensusBadgeClass(status) { switch (status) { case 'resolved': return 'status-badge resolved'; case 'conflict': return 'status-badge conflict'; 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 display text for a consensus status */ getConsensusText(status) { switch (status) { case 'resolved': return 'Resolved'; case 'conflict': return 'Conflict'; case 'insufficient_quorum': return 'Insufficient Quorum'; case 'tie': return 'Tie'; case 'no_claims': return 'No Claims'; case 'error': return 'Error'; default: return 'Unknown'; } }, /** * Get hex color for a consensus status */ getConsensusColor(status) { switch (status) { case 'resolved': return '#10b981'; case 'conflict': return '#ef4444'; case 'insufficient_quorum': return '#eab308'; case 'tie': return '#f97316'; case 'no_claims': return '#6b7280'; case 'error': return '#ef4444'; default: return '#6b7280'; } } }; /** * Legacy compatibility layers */ window.renderStatusBadge = window.sdk.utils.status.renderStatusBadge; window.truncateUrl = (url, maxLength) => window.sdk.utils.dom.truncate(url, maxLength); window.escapeHtml = window.sdk.utils.dom.escapeHtml; window.formatUptime = window.sdk.utils.format.formatUptime; window.formatDuration = window.sdk.utils.format.formatDuration; window.formatCPUUsage = window.sdk.utils.format.formatCPUUsage; window.formatMemoryUsage = window.sdk.utils.format.formatMemoryUsage; // Default chart colors if not defined window.chartColors = 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 = 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)' }; window.getChartColors = () => { return document.documentElement.classList.contains('dark') ? window.darkModeColors : window.chartColors; };