(config)
| 12431 | files.forEach(f => { |
| 12432 | if (!grouped[f.category]) grouped[f.category] = []; |
| 12433 | grouped[f.category].push(f); |
| 12434 | }); |
| 12435 | |
| 12436 | let html = ''; |
| 12437 | const categoryOrder = ['data_stolen', 'credentials', 'vulnerabilities', 'scan_results']; |
| 12438 | |
| 12439 | categoryOrder.forEach(cat => { |
| 12440 | if (!grouped[cat] || grouped[cat].length === 0) return; |
| 12441 | const meta = NETWORK_FILE_CATEGORY_LABELS[cat] || { label: cat, color: 'text-gray-300' }; |
| 12442 | |
| 12443 | html += ` |
| 12444 | <div class="mb-6"> |
| 12445 | <h4 class="text-sm font-semibold uppercase tracking-wider ${meta.color} mb-3">${meta.label} (${grouped[cat].length})</h4> |
| 12446 | <div class="space-y-2">`; |
| 12447 | |
| 12448 | grouped[cat].forEach(file => { |
| 12449 | const fname = escapeHtml(file.filename); |
| 12450 | const size = escapeHtml(file.size || ''); |
| 12451 | const mod = escapeHtml(file.modified || ''); |
| 12452 | const vpath = file.virtual_path ? encodeURIComponent(file.virtual_path) : ''; |
| 12453 | const clickable = !!vpath; |
| 12454 | |
| 12455 | html += ` |
| 12456 | <div class="flex items-center justify-between bg-gray-800 rounded-lg px-4 py-3 ${clickable ? 'hover:bg-gray-700 cursor-pointer' : ''} transition-colors" |
| 12457 | ${clickable ? `onclick="openLootFile('${escapeAttr(vpath)}')"` : ''}> |
| 12458 | <div class="flex items-center gap-3 min-w-0"> |
| 12459 | <svg class="w-4 h-4 text-gray-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 12460 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> |
| 12461 | </svg> |
| 12462 | <span class="text-sm text-white truncate" title="${fname}">${fname}</span> |
| 12463 | </div> |
| 12464 | <div class="flex items-center gap-4 text-xs text-gray-400 flex-shrink-0 ml-4"> |
| 12465 | ${size ? `<span>${size}</span>` : ''} |
| 12466 | ${mod ? `<span class="hidden sm:inline">${mod}</span>` : ''} |
| 12467 | ${clickable ? `<svg class="w-4 h-4 text-Ragnar-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path></svg>` : ''} |
| 12468 | </div> |
| 12469 | </div>`; |
| 12470 | }); |
| 12471 | |
| 12472 | html += `</div></div>`; |
| 12473 | }); |
| 12474 | |
| 12475 | container.innerHTML = html; |
| 12476 | } |
| 12477 | |
| 12478 | |
| 12479 | async function updateNetworkStatusBanner() { |
| 12480 | try { |
| 12481 | const configResponse = await fetchAPI('/api/config'); |
| 12482 | if (configResponse && configResponse.success) { |
| 12483 | const maxFailedPings = configResponse.config.network_max_failed_pings || 15; |
| 12484 | const offlineMinutes = Math.round((maxFailedPings * 60) / 60); // 60 second ARP scans |
| 12485 | |
| 12486 | const failedPingsEl = document.getElementById('network-status-failed-pings'); |
| 12487 | const offlineTimeEl = document.getElementById('network-status-offline-time'); |
| 12488 | |
| 12489 | if (failedPingsEl) { |
| 12490 | failedPingsEl.textContent = maxFailedPings; |
no test coverage detected