export function formatBytes(bytes) { if (!bytes || bytes === 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`; } export function formatUptime(seconds) { if (!seconds) return '—'; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); if (h > 0) return `${h}h ${m}m`; return `${m}m`; } export function protoName(n) { const PROTOS = { 1:'ICMP', 2:'IGMP', 6:'TCP', 17:'UDP', 41:'IPv6', 47:'GRE', 50:'ESP', 51:'AH', 58:'ICMPv6', 89:'OSPF', 132:'SCTP' }; return PROTOS[n] || (n ? `IP/${n}` : '—'); } export function protoColor(n) { const COLORS = { 6:'#38bdf8', 17:'#a78bfa', 1:'#fbbf24', 58:'#fb923c' }; return COLORS[n] || '#64748b'; } export function serviceLabel(port, proto) { const TCP = { 20:'FTP-data', 21:'FTP', 22:'SSH', 23:'Telnet', 25:'SMTP', 53:'DNS', 80:'HTTP', 110:'POP3', 143:'IMAP', 443:'HTTPS', 465:'SMTPS', 587:'SMTP', 993:'IMAPS', 995:'POP3S', 1194:'OpenVPN', 1883:'MQTT', 3306:'MySQL', 3389:'RDP', 5900:'VNC', 6443:'K8s API', 8080:'HTTP-alt', 8443:'HTTPS-alt', 8883:'MQTT-TLS', 9000:'Custom', 51820:'WireGuard', }; const UDP = { 53:'DNS', 67:'DHCP', 68:'DHCP', 123:'NTP', 161:'SNMP', 500:'IKE', 514:'Syslog', 1194:'OpenVPN', 4500:'IPsec', 5353:'mDNS', 51820:'WireGuard', }; if (proto === 6 && TCP[port]) return TCP[port]; if (proto === 17 && UDP[port]) return UDP[port]; if (TCP[port]) return TCP[port]; return port ? `port ${port}` : ''; } export const DEVICE_GROUPS = ['IoT', 'Servers', 'Mobile', 'Network', 'Desktop', 'Media']; export const GROUP_COLORS = { IoT:'#f59e0b', Servers:'#3b82f6', Mobile:'#ec4899', Network:'#10b981', Desktop:'#8b5cf6', Media:'#f97316', }; export function timeAgo(ms) { if (!ms) return '—'; const sec = Math.floor((Date.now() - ms) / 1000); if (sec < 10) return 'just now'; if (sec < 60) return `${sec}s ago`; const min = Math.floor(sec / 60); if (min < 60) return `${min}m ago`; return `${Math.floor(min / 60)}h ago`; } export function deviceIcon(device) { if (device.is_wired) return '🖥'; const oui = (device.oui || '').toLowerCase(); if (oui.includes('apple')) return '🍎'; if (oui.includes('samsung')) return '📱'; if (oui.includes('sony')) return '🎮'; if (oui.includes('lg')) return '📺'; if (oui.includes('intel')) return '💻'; if (oui.includes('raspberry')) return '🍓'; return '📡'; } // Map app/category codes to readable names const APP_NAMES = { 0: 'Other', 1: 'Instant messaging', 2: 'P2P', 3: 'Thumbnails', 4: 'Streaming media', 5: 'VoIP', 6: 'Gaming', 7: 'Remote access', 8: 'News feeds', 9: 'Streaming video', 10: 'Email', 11: 'News', 12: 'Downloads', 13: 'Business apps', 14: 'Social networking', 15: 'Web browsing', 255: 'Unknown', }; export function appName(cat) { if (typeof cat === 'number') return APP_NAMES[cat] || `Cat ${cat}`; return cat || 'Unknown'; } export const OUT_COLOR = '#38bdf8'; export const IN_COLOR = '#f472b6';