(files, path, highlightFile = null)
| 13230 | function _pwnFileSizeLabel(bytes) { |
| 13231 | if (!bytes || bytes <= 0) return ''; |
| 13232 | if (bytes < 1024) return bytes + ' B'; |
| 13233 | if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; |
| 13234 | return (bytes / 1048576).toFixed(1) + ' MB'; |
| 13235 | } |
| 13236 | |
| 13237 | function _pwnFilesHTML(files) { |
| 13238 | if (!Array.isArray(files) || files.length === 0) return ''; |
| 13239 | return '<div class="flex flex-wrap gap-2 mt-2">' + files.map(f => { |
| 13240 | const name = escapeHtml(f.name || ''); |
| 13241 | const size = _pwnFileSizeLabel(f.size); |
| 13242 | const sizeLabel = size ? ` (${size})` : ''; |
| 13243 | const encodedName = encodeURIComponent(f.name || ''); |
| 13244 | return `<a href="/api/pwnagotchi/download?file=${encodedName}" download |
| 13245 | class="inline-flex items-center gap-1 px-2 py-1 rounded text-xs bg-slate-700 hover:bg-slate-600 text-slate-200 hover:text-white transition-colors" |
| 13246 | title="Download ${name}${sizeLabel}"> |
| 13247 | <svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg> |
| 13248 | <span class="truncate max-w-[10rem]">${name}</span></a>`; |
| 13249 | }).join('') + '</div>'; |
| 13250 | } |
| 13251 | |
| 13252 | function displayPwnNetworksTable(networks) { |
| 13253 | const container = document.getElementById('pwn-networks-table'); |
| 13254 | if (!container) return; |
| 13255 | |
| 13256 | if (!Array.isArray(networks) || networks.length === 0) { |
| 13257 | container.innerHTML = '<p class="text-gray-400">No Pwnagotchi captures found yet.</p>'; |
| 13258 | return; |
| 13259 | } |
| 13260 | |
| 13261 | const previewCount = 10; |
| 13262 | const hasMore = networks.length > previewCount; |
| 13263 | const previewItems = networks.slice(0, previewCount); |
| 13264 | const hiddenItems = hasMore ? networks.slice(previewCount) : []; |
| 13265 | |
| 13266 | /* --- Mobile card layout (shown < md) --- */ |
| 13267 | function networkCardHTML(net) { |
| 13268 | const ssid = escapeHtml(net.ssid || 'Unknown'); |
| 13269 | const bssid = escapeHtml(net.bssid || 'Unknown'); |
| 13270 | const lastSeen = net.last_seen ? formatTimestamp(net.last_seen) : 'Unknown'; |
| 13271 | return ` |
| 13272 | <div class="bg-slate-800 bg-opacity-60 rounded-lg p-4"> |
| 13273 | <div class="flex items-start justify-between gap-2 mb-2"> |
| 13274 | <span class="text-white font-semibold text-sm break-all">${ssid}</span> |
| 13275 | <span class="text-xs text-gray-400 whitespace-nowrap">${lastSeen}</span> |
| 13276 | </div> |
| 13277 | <div class="text-xs text-gray-400 font-mono mb-2">${bssid}</div> |
| 13278 | <div class="flex flex-wrap gap-1 mb-1">${_pwnBadgesHTML(net)}</div> |
| 13279 | <div class="text-xs mt-1">${_pwnGpsHTML(net)}</div> |
| 13280 | ${_pwnFilesHTML(net.files)} |
| 13281 | </div>`; |
| 13282 | } |
| 13283 | |
| 13284 | /* --- Desktop table row (shown >= md) --- */ |
| 13285 | function networkRowHTML(net) { |
| 13286 | const ssid = escapeHtml(net.ssid || 'Unknown'); |
| 13287 | const bssid = escapeHtml(net.bssid || 'Unknown'); |
| 13288 | const lastSeen = net.last_seen ? formatTimestamp(net.last_seen) : 'Unknown'; |
| 13289 | return ` |
no test coverage detected