const { invoke } = window.__TAURI__.core; const { listen } = window.__TAURI__.event; var theme = (() => { var STORAGE_KEY = 'argus-theme'; function get() { try { return localStorage.getItem(STORAGE_KEY) || 'cyber'; } catch (e) { return 'cyber'; } } function set(name) { if (name === 'cyber') { document.documentElement.removeAttribute('data-theme'); } else { document.documentElement.setAttribute('data-theme', name); } try { localStorage.setItem(STORAGE_KEY, name); } catch (e) { } } function init() { var saved = get(); set(saved); var picker = document.getElementById('theme-picker'); picker.value = saved; picker.addEventListener('change', function () { set(picker.value); }); } // Apply immediately (before DOMContentLoaded) to prevent flash set(get()); return { init }; })(); const table = (() => { var dirty = false; var flushTimer = null; var ipIndex = {}; let data = []; let filtered = []; let sortKey = 'ip'; let sortDir = 'asc'; let filterText = ''; let checkedIps = new Set(); var ROW_HEIGHT = 32; var BUFFER_ROWS = 20; var scrollContainer = null; function ipToNum(ip) { const parts = String(ip).split('.'); if (parts.length !== 4) return 0; return parts.reduce((acc, o) => (acc << 8) + parseInt(o, 10), 0) >>> 0; } function getSortVal(host, key) { switch (key) { case 'ip': return ipToNum(host.ip); case 'status': return host.status || ''; case 'rtt': return host.ping ? host.ping.rtt_ms : 99999; case 'hostname': return (host.hostname || '').toLowerCase(); case 'ports': return host.ports ? host.ports.filter((p) => p.state === 'Open').length : 0; default: return ''; } } function formatPorts(host) { if (!host.ports || host.ports.length === 0) return '-'; const open = host.ports.filter((p) => p.state === 'Open'); if (open.length === 0) return '-'; return open.map((p) => { const svc = p.service ? p.service : p.port; return '' + p.port + '/' + (p.service || 'unknown') + ''; }).join(' '); } function buildDetailHtml(host) { let html = '
'; html += '
'; html += '' + host.ip + ''; if (host.hostname) html += '' + host.hostname + ''; if (host.ping) html += '' + host.ping.rtt_ms.toFixed(1) + ' ms'; html += '
'; if (host.netbios) { html += '
'; if (host.netbios.computer_name) html += 'Computer: ' + host.netbios.computer_name + ''; if (host.netbios.workgroup) html += 'Workgroup: ' + host.netbios.workgroup + ''; if (host.netbios.mac_from_netbios) html += 'MAC: ' + host.netbios.mac_from_netbios + ''; html += '
'; } if (host.ports && host.ports.length > 0) { const open = host.ports.filter((p) => p.state === 'Open'); const closed = host.ports.filter((p) => p.state === 'Closed'); const filtered = host.ports.filter((p) => p.state === 'Filtered'); if (open.length > 0) { html += ''; html += ''; html += ''; html += ''; for (const p of open) { html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; } html += '
PortProtocolStateServiceBanner
' + p.port + '' + p.protocol + '' + p.state + '' + (p.service || '-') + '' + (p.banner || '-') + '
'; } html += '
'; html += '' + open.length + ' open'; html += '' + closed.length + ' closed'; html += '' + filtered.length + ' filtered'; html += '
'; } else { html += '

No port scan data. Enable "Simple Port Scan" and rescan.

'; } html += '
'; return html; } let expandedIp = null; function handleRowClick(e) { const row = e.target.closest('tr'); if (!row || !row.dataset.ip) return; const ip = row.dataset.ip; // Remove any existing detail row const existing = document.querySelector('.detail-row'); if (existing) existing.remove(); // If clicking the same row, just collapse if (expandedIp === ip) { expandedIp = null; row.classList.remove('selected'); return; } // Deselect previous const prev = document.querySelector('#results-body tr.selected'); if (prev) prev.classList.remove('selected'); // Find the host data const host = data.find((h) => String(h.ip) === ip); if (!host) return; // Mark selected row.classList.add('selected'); expandedIp = ip; // Insert detail row after the clicked row const detailRow = document.createElement('tr'); detailRow.className = 'detail-row'; const td = document.createElement('td'); td.colSpan = document.querySelectorAll('#results-table th').length; td.innerHTML = buildDetailHtml(host); detailRow.appendChild(td); row.after(detailRow); } function refresh() { if (filterText) { const q = filterText.toLowerCase(); filtered = data.filter((h) => String(h.ip).includes(q) || (h.hostname || '').toLowerCase().includes(q) || (h.status || '').toLowerCase().includes(q) ); } else { filtered = [...data]; } filtered.sort((a, b) => { const va = getSortVal(a, sortKey); const vb = getSortVal(b, sortKey); let cmp = 0; if (typeof va === 'number' && typeof vb === 'number') { cmp = va - vb; } else { cmp = String(va).localeCompare(String(vb)); } return sortDir === 'asc' ? cmp : -cmp; }); render(); } function render() { var tbody = document.getElementById('results-body'); var count = document.getElementById('host-count'); if (!tbody) return; expandedIp = null; if (!scrollContainer) { scrollContainer = document.getElementById('results-table-container'); scrollContainer.addEventListener('scroll', onScroll); } renderVisible(); if (count) { var up = data.filter(function (h) { return h.status === 'Up'; }).length; count.textContent = up + ' up / ' + data.length + ' total'; } if (columns && columns.apply) columns.apply(); } function renderVisible() { var tbody = document.getElementById('results-body'); if (!tbody || !scrollContainer) return; var totalRows = filtered.length; var viewHeight = scrollContainer.clientHeight; var scrollTop = scrollContainer.scrollTop; var headerHeight = 36; var startIdx = Math.max(0, Math.floor((scrollTop - headerHeight) / ROW_HEIGHT) - BUFFER_ROWS); var visibleCount = Math.ceil(viewHeight / ROW_HEIGHT) + (BUFFER_ROWS * 2); var endIdx = Math.min(totalRows, startIdx + visibleCount); var topSpace = startIdx * ROW_HEIGHT; var bottomSpace = (totalRows - endIdx) * ROW_HEIGHT; var colCount = document.querySelectorAll('#results-table th').length; var html = ''; if (topSpace > 0) { html += ''; } for (var i = startIdx; i < endIdx; i++) { var host = filtered[i]; var ip = host.ip; var status = host.status || 'Unknown'; var cls = 'status-' + status.toLowerCase(); var rtt = host.ping ? host.ping.rtt_ms.toFixed(1) : '-'; var hostname = host.hostname || '-'; var isChecked = checkedIps.has(ip) ? 'checked' : ''; html += '' + '' + '' + ip + '' + '' + status + '' + '' + rtt + '' + '' + hostname + '' + '' + formatPorts(host) + '' + ''; } if (bottomSpace > 0) { html += ''; } tbody.innerHTML = html; } var scrollTimeout = null; function onScroll() { if (scrollTimeout) return; scrollTimeout = requestAnimationFrame(function () { scrollTimeout = null; renderVisible(); if (columns && columns.apply) columns.apply(); }); } function upsert(host) { var idx = data.findIndex(function (h) { return h.ip === host.ip; }); if (idx >= 0) { data[idx] = host; } else { ipIndex[host.ip] = data.length; data.push(host); } dirty = true; if (!flushTimer) { flushTimer = setTimeout(flush, 200); } } function flush() { flushTimer = null; if (!dirty) return; dirty = false; refresh(); } function clear() { data = []; filtered = []; checkedIps.clear(); ipIndex = {}; document.getElementById('check-all').checked = false; refresh(); } function setSort(key) { if (sortKey === key) { sortDir = sortDir === 'asc' ? 'desc' : 'asc'; } else { sortKey = key; sortDir = 'asc'; } document.querySelectorAll('#results-table th').forEach((th) => { th.classList.remove('sorted-asc', 'sorted-desc'); if (th.dataset.sort === sortKey) { th.classList.add(sortDir === 'asc' ? 'sorted-asc' : 'sorted-desc'); } }); refresh(); } function setFilter(text) { filterText = text; refresh(); } return { upsert, clear, setSort, setFilter, handleRowClick, init: () => { document.getElementById('results-body') .addEventListener('click', (e) => { // Don't expand detail panel when clicking a checkbox if (e.target.classList.contains('row-check')) { const row = e.target.closest('tr'); if (row && row.dataset.ip) { if (e.target.checked) { checkedIps.add(row.dataset.ip); } else { checkedIps.delete(row.dataset.ip); } } return; } handleRowClick(e); }); // Select all checkbox document.getElementById('check-all').addEventListener('change', (e) => { if (e.target.checked) { filtered.forEach((h) => checkedIps.add(String(h.ip))); } else { checkedIps.clear(); } render(); }); }, getAll: () => [...data], getUp: () => data.filter((h) => h.status === 'Up'), getIPs: () => data.map((h) => String(h.ip)), getUpIPs: () => data.filter((h) => h.status === 'Up').map((h) => String(h.ip)), getChecked: () => data.filter((h) => checkedIps.has(String(h.ip))), getCheckedIPs: () => [...checkedIps], }; })(); const scanner = (() => { let scanning = false; const els = { input: () => document.getElementById('ip-range-input'), btnScan: () => document.getElementById('btn-scan'), btnStop: () => document.getElementById('btn-stop'), status: () => document.getElementById('status-text'), bar: () => document.getElementById('progress-bar'), fill: () => document.getElementById('progress-fill'), }; function setScanning(active) { scanning = active; els.btnScan().disabled = active; els.btnStop().disabled = !active; els.input().disabled = active; els.bar().hidden = !active; if (!active) els.fill().style.width = '0%'; } async function start() { const range = els.input().value.trim(); if (!range) { els.status().textContent = 'Enter an IP range'; return; } table.clear(); setScanning(true); els.status().textContent = 'Scanning...'; try { const portScan = document.getElementById('chk-portscan').checked; await invoke('start_scan', { config: { range: range, timeout_ms: 1500, port_scan: portScan, ports: null, port_timeout_ms: 1000, }, }); } catch (err) { els.status().textContent = 'Error: ' + err; setScanning(false); } } async function stop() { try { await invoke('stop_scan'); } catch (err) { console.error('stop_scan error:', err); } els.status().textContent = 'Scan stopped'; setScanning(false); } async function setupListeners() { await listen('scan:host-found', (e) => { table.upsert(e.payload); }); await listen('scan:progress', (e) => { const p = e.payload; const pct = Math.round((p.done / p.total) * 100); els.fill().style.width = pct + '%'; els.status().textContent = 'Scanning... ' + p.done + ' / ' + p.total; }); await listen('scan:complete', (e) => { els.status().textContent = 'Scan complete (' + e.payload.total_hosts + ' hosts)'; setScanning(false); }); } function init() { els.btnScan().addEventListener('click', start); els.btnStop().addEventListener('click', stop); els.input().addEventListener('keydown', (e) => { if (e.key === 'Enter' && !scanning) start(); }); setupListeners(); } return { init }; })(); const exporter = (() => { async function csvAll() { const data = table.getAll(); if (data.length === 0) return; await saveCSV(data); } async function csvUp() { const data = table.getUp(); if (data.length === 0) return; await saveCSV(data); } async function saveCSV(data) { const { save } = window.__TAURI__.dialog; const path = await save({ defaultPath: 'argus-scan.csv', filters: [{ name: 'CSV', extensions: ['csv'] }], }); if (!path) return; try { await invoke('export_csv', { request: { path: path, hosts: data } }); } catch (err) { console.error('Export error:', err); } } return { csvAll, csvUp }; })(); const columns = (() => { const STORAGE_KEY = 'argus-columns'; const allCols = ['ip', 'status', 'rtt', 'hostname', 'ports']; function getVisible() { try { const saved = localStorage.getItem(STORAGE_KEY); if (saved) return JSON.parse(saved); } catch (e) { } return [...allCols]; } function save(visible) { localStorage.setItem(STORAGE_KEY, JSON.stringify(visible)); } function apply() { const visible = getVisible(); allCols.forEach((col, i) => { const show = visible.includes(col); // Table headers const th = document.querySelector('#results-table th[data-sort="' + col + '"]'); if (th) th.style.display = show ? '' : 'none'; // Table body cells (by column index) const colIndex = allCols.indexOf(col) + 2; document.querySelectorAll('#results-table tbody td:nth-child(' + colIndex + ')').forEach((td) => { td.style.display = show ? '' : 'none'; }); }); } function init() { const btn = document.getElementById('btn-columns'); const picker = document.getElementById('col-picker'); const visible = getVisible(); // Set initial checkbox state from saved prefs picker.querySelectorAll('input[type="checkbox"]').forEach((cb) => { if (!cb.disabled) { cb.checked = visible.includes(cb.dataset.col); } }); // Toggle picker btn.addEventListener('click', (e) => { e.stopPropagation(); picker.hidden = !picker.hidden; }); // Close picker when clicking outside document.addEventListener('click', (e) => { if (!picker.contains(e.target) && e.target !== btn) { picker.hidden = true; } }); // Handle checkbox changes picker.addEventListener('change', (e) => { const cb = e.target; if (!cb.dataset.col) return; const current = getVisible(); if (cb.checked) { if (!current.includes(cb.dataset.col)) current.push(cb.dataset.col); } else { const idx = current.indexOf(cb.dataset.col); if (idx >= 0) current.splice(idx, 1); } save(current); apply(); }); // Apply saved preferences on load apply(); } return { init, apply }; })(); const sender = (() => { function init() { const menus = [ { btn: 'btn-send-nmap', menu: 'send-nmap-menu' }, { btn: 'btn-send-monitor', menu: 'send-monitor-menu' }, ]; menus.forEach(({ btn, menu }) => { const btnEl = document.getElementById(btn); const menuEl = document.getElementById(menu); btnEl.addEventListener('click', (e) => { e.stopPropagation(); // Close any other open send menus document.querySelectorAll('.send-menu').forEach((m) => { if (m !== menuEl) m.hidden = true; }); menuEl.hidden = !menuEl.hidden; }); }); // Close menus on outside click document.addEventListener('click', () => { document.querySelectorAll('.send-menu').forEach((m) => m.hidden = true); }); // Handle send actions document.querySelectorAll('.send-menu .ctx-item').forEach((item) => { item.addEventListener('click', (e) => { const action = e.target.dataset.send; let hosts; if (action.endsWith('-all')) hosts = table.getAll(); else if (action.endsWith('-up')) hosts = table.getUp(); else if (action.endsWith('-checked')) hosts = table.getChecked(); if (!hosts || hosts.length === 0) { console.log('No hosts to send'); return; } if (action.startsWith('nmap')) { nmapTab.setTarget(hosts.map((h) => String(h.ip))); } else if (action.startsWith('monitor')) { monitorTab.setTargets(hosts); } document.querySelectorAll('.send-menu').forEach((m) => m.hidden = true); }); }); } return { init }; })(); const ctxMenu = (() => { const menu = () => document.getElementById('context-menu'); let targetHost = null; function show(e, host) { e.preventDefault(); targetHost = host; // Show/hide HTTP/HTTPS based on open ports const openPorts = (host.ports || []) .filter((p) => p.state === 'Open') .map((p) => p.port); const hasHttp = openPorts.some((p) => [80, 8080, 8088].includes(p)); const hasHttps = openPorts.some((p) => [443, 8443].includes(p)); menu().querySelector('[data-action="open-http"]').style.display = hasHttp ? '' : 'none'; menu().querySelector('[data-action="open-https"]').style.display = hasHttps ? '' : 'none'; const el = menu(); el.hidden = false; // Position near the cursor, but keep it on screen let x = e.clientX; let y = e.clientY; const rect = el.getBoundingClientRect(); if (x + rect.width > window.innerWidth) x = window.innerWidth - rect.width - 4; if (y + rect.height > window.innerHeight) y = window.innerHeight - rect.height - 4; el.style.left = x + 'px'; el.style.top = y + 'px'; } function hide() { menu().hidden = true; targetHost = null; } async function handleAction(action) { if (!targetHost) return; const ip = String(targetHost.ip); const hostname = targetHost.hostname || ''; switch (action) { case 'copy-ip': await navigator.clipboard.writeText(ip); break; case 'copy-hostname': await navigator.clipboard.writeText(hostname || ip); break; case 'open-http': await invoke('launch_url', { url: 'http://' + ip }); break; case 'open-https': await invoke('launch_url', { url: 'https://' + ip }); break; case 'traceroute': traceroute.run(ip); break; case 'send-monitor': var monHost = table.getAll().find(function (h) { return String(h.ip) === ip; }); monitorTab.setTargets(monHost ? [monHost] : [ip]); break; case 'send-nmap': nmapTab.setTarget([ip]); break; } hide(); } function init() { // Hide on any click outside document.addEventListener('click', hide); // Handle menu item clicks menu().addEventListener('click', (e) => { const item = e.target.closest('.ctx-item'); if (item) handleAction(item.dataset.action); }); // Right-click on table rows document.getElementById('results-body').addEventListener('contextmenu', (e) => { const row = e.target.closest('tr'); if (!row || !row.dataset.ip) return; const host = table.getAll().find((h) => String(h.ip) === row.dataset.ip); if (host) show(e, host); }); } return { init }; })(); const traceroute = (() => { const modal = () => document.getElementById('traceroute-modal'); const output = () => document.getElementById('traceroute-output'); const target = () => document.getElementById('traceroute-target'); let running = false; async function run(ip) { modal().classList.add('visible'); output().innerHTML = ''; output().classList.add('traceroute-running'); target().textContent = ip; running = true; try { await invoke('run_traceroute', { ip: ip }); } catch (err) { appendLine('Error: ' + err); } } function appendLine(text) { const el = output(); const line = document.createElement('div'); line.className = 'traceroute-line'; if (text.includes('reached')) line.classList.add('hop-reached'); line.textContent = text; el.appendChild(line); el.scrollTop = el.scrollHeight; } function close() { modal().classList.remove('visible'); output().classList.remove('traceroute-running'); running = false; } async function setupListeners() { await listen('traceroute:hop', (e) => { appendLine(e.payload); }); await listen('traceroute:complete', () => { output().classList.remove('traceroute-running'); running = false; }); await listen('traceroute:error', (e) => { appendLine('Error: ' + e.payload); output().classList.remove('traceroute-running'); running = false; }); } function init() { document.getElementById('traceroute-close').addEventListener('click', close); // Close on overlay click modal().addEventListener('click', (e) => { if (e.key === 'Escape' && !modal().hidden) close(); }); // Close on Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !modal().hidden) close(); }); setupListeners(); } return { init, run }; })(); const nmapTab = (() => { let running = false; const els = { target: () => document.getElementById('nmap-target'), preset: () => document.getElementById('nmap-preset'), custom: () => document.getElementById('nmap-custom-args'), btnScan: () => document.getElementById('btn-nmap-scan'), btnStop: () => document.getElementById('btn-nmap-stop'), output: () => document.getElementById('nmap-output'), }; function getArgs() { const preset = els.preset().value; switch (preset) { case 'quick': return ['-sT', '-T4']; case 'service': return ['-sT', '-sV']; case 'os': return ['-sT', '-O']; case 'full': return ['-sT', '-sV', '-sC']; case 'custom': return els.custom().value.trim().split(/\s+/).filter(Boolean); default: return ['-sT']; } } function setRunning(active) { try { running = active; els.btnScan().disabled = active; els.btnStop().disabled = !active; els.target().disabled = active; els.preset().disabled = active; if (active) { els.output().classList.add('nmap-running'); } else { els.output().classList.remove('nmap-running'); } var nmapTabEl = document.querySelector('[data-tab="nmap"]'); console.log('found tab:', nmapTabEl); if (active) { nmapTabEl.classList.add('tab-running'); } else { nmapTabEl.classList.remove('tab-running'); } console.log('tab class now:', nmapTabEl.classList.toString()); } catch (err) { console.error('setRunning error:', err); } } function appendLine(text) { const el = els.output(); const line = document.createElement('div'); line.className = 'nmap-line'; if (text.match(/^\d+\/tcp\s+open/)) { line.classList.add('nmap-line-open'); } else if (text.match(/^\d+\/tcp\s+closed/)) { line.classList.add('nmap-line-closed'); } else if (text.startsWith('Nmap scan report') || text.startsWith('PORT')) { line.classList.add('nmap-line-header'); } else if (text.startsWith('|') || text.startsWith('Service Info')) { line.classList.add('nmap-line-info'); } line.textContent = text; el.appendChild(line); el.scrollTop = el.scrollHeight; } async function start() { const target = els.target().value.trim(); if (!target) return; els.output().innerHTML = ''; setRunning(true); try { const args = getArgs(); if (document.getElementById('chk-nmap-verbose').checked) { args.unshift('-v'); } await invoke('run_nmap', { config: { target: target, args: args }, }); } catch (err) { appendLine('Error: ' + err); setRunning(false); } } async function stop() { try { await invoke('stop_nmap'); } catch (err) { console.error('stop_nmap error:', err); } } function setTarget(ips) { els.target().value = ips.join(' '); // Switch to nmap tab document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active')); document.querySelectorAll('.view').forEach((v) => v.classList.remove('active')); document.querySelector('[data-tab="nmap"]').classList.add('active'); document.getElementById('view-nmap').classList.add('active'); } async function setupListeners() { await listen('nmap:line', (e) => { appendLine(e.payload); }); await listen('nmap:complete', (e) => { appendLine(''); appendLine(e.payload); setRunning(false); }); await listen('nmap:error', (e) => { appendLine('Error: ' + e.payload); setRunning(false); }); } function init() { els.btnScan().addEventListener('click', start); els.btnStop().addEventListener('click', stop); els.target().addEventListener('keydown', (e) => { if (e.key === 'Enter' && !running) start(); }); // Show/hide custom args field els.preset().addEventListener('change', () => { els.custom().hidden = els.preset().value !== 'custom'; }); setupListeners(); } return { init, setTarget }; })(); const monitorTab = (() => { let running = false; let entries = {}; var els = { input: () => document.getElementById('monitor-input'), interval: () => document.getElementById('monitor-interval'), btnAdd: () => document.getElementById('btn-monitor-add'), btnStart: () => document.getElementById('btn-monitor-start'), btnStop: () => document.getElementById('btn-monitor-stop'), btnClear: () => document.getElementById('btn-monitor-clear'), body: () => document.getElementById('monitor-body'), }; function setRunning(active) { running = active; els.btnStart().disabled = active; els.btnStop().disabled = !active; els.interval().disabled = active; var monTabEl = document.querySelector('[data-tab="monitor"]'); if (active) { monTabEl.classList.add('tab-running'); } else { monTabEl.classList.remove('tab-running'); } } function render() { var body = els.body(); var ips = Object.keys(entries).sort(); var rows = ips.map(function (ip) { var e = entries[ip]; var statusClass = 'monitor-status-' + e.status; var lastRtt = e.last_rtt_ms !== null ? e.last_rtt_ms.toFixed(1) : '-'; var minRtt = e.min_rtt_ms !== null ? e.min_rtt_ms.toFixed(1) : '-'; var avgRtt = e.avg_rtt_ms !== null ? e.avg_rtt_ms.toFixed(1) : '-'; var maxRtt = e.max_rtt_ms !== null ? e.max_rtt_ms.toFixed(1) : '-'; var loss = e.loss_pct.toFixed(1); var label = e.label || '-'; return '' + '' + ip + '' + '' + label + '' + '' + e.status + '' + '' + lastRtt + '' + '' + minRtt + '' + '' + avgRtt + '' + '' + maxRtt + '' + '' + e.packets_sent + '' + '' + e.packets_received + '' + '' + loss + '' + '' + e.consecutive_failures + '' + '' + ''; }); body.innerHTML = rows.join(''); } async function addHosts() { var input = els.input().value.trim(); if (!input) return; var hosts = input.split(',').map(function (s) { return { ip: s.trim(), label: null }; }).filter(function (h) { return h.ip.length > 0; }); try { await invoke('monitor_add_hosts', { hosts: hosts }); for (var i = 0; i < hosts.length; i++) { try { var hostname = await invoke('monitor_resolve_host', { ip: hosts[i].ip }); if (hostname) { await invoke('monitor_update_label', { ip: hosts[i].ip, label: hostname }); } } catch (e) { } } var updated = await invoke('monitor_get_entries'); entries = {}; updated.forEach(function (e) { entries[e.ip] = e; }); render(); els.input().value = ''; } catch (err) { console.error('Failed to add hosts:', err); } } async function removeHost(ip) { try { await invoke('monitor_remove_host', { ip: ip }); delete entries[ip]; render(); } catch (err) { console.error('Failed to remove host:', err); } } async function start() { if (Object.keys(entries).length === 0) return; var interval = parseInt(els.interval().value, 10); setRunning(true); try { await invoke('monitor_start', { intervalMs: interval }); } catch (err) { console.error('Monitor start error:', err); setRunning(false); } } async function stop() { try { await invoke('monitor_stop'); } catch (err) { console.error('Monitor stop error:', err); } setRunning(false); } async function clearAll() { try { if (running) await stop(); await invoke('monitor_clear'); entries = {}; render(); } catch (err) { console.error('Monitor clear error:', err); } } function setTargets(hosts) { var items = hosts.map(function (h) { if (typeof h === 'string') return { ip: h, label: null }; return { ip: String(h.ip), label: h.hostname || null }; }); invoke('monitor_add_hosts', { hosts: items }).then(function () { return invoke('monitor_get_entries'); }).then(function (updated) { entries = {}; updated.forEach(function (e) { entries[e.ip] = e; }); render(); }); document.querySelectorAll('.tab').forEach(function (t) { t.classList.remove('active'); }); document.querySelectorAll('.view').forEach(function (v) { v.classList.remove('active'); }); document.querySelector('[data-tab="monitor"]').classList.add('active'); document.getElementById('view-monitor').classList.add('active'); } async function setupListeners() { await listen('monitor:update', function (e) { var entry = e.payload; entries[entry.ip] = entry; render(); }); await listen('monitor:state-change', function (e) { var data = e.payload; console.log('State change:', data.ip, data.old, '->', data.new); }); } function init() { els.btnAdd().addEventListener('click', addHosts); els.btnStart().addEventListener('click', start); els.btnStop().addEventListener('click', stop); els.btnClear().addEventListener('click', clearAll); els.input().addEventListener('keydown', function (e) { if (e.key === 'Enter') addHosts(); }); // Remove button delegation els.body().addEventListener('click', function (e) { var btn = e.target.closest('.btn-remove'); if (btn) removeHost(btn.dataset.ip); }); els.body().addEventListener('dblclick', function (e) { var cell = e.target.closest('.editable-label'); if (!cell) return; var ip = cell.dataset.ip; var currentLabel = cell.textContent === '-' ? '' : cell.textContent; var input = document.createElement('input'); input.type = 'text'; input.value = currentLabel; input.className = 'label-edit-input'; cell.textContent = ''; cell.appendChild(input); input.focus(); input.select(); function save() { var newLabel = input.value.trim(); invoke('monitor_update_label', { ip: ip, label: newLabel }).then(function () { return invoke('monitor_get_entries'); }).then(function (updated) { entries = {}; updated.forEach(function (e) { entries[e.ip] = e; }); render(); }); } input.addEventListener('blur', save); input.addEventListener('keydown', function (ev) { if (ev.key === 'Enter') save(); if (ev.key === 'Escape') render(); }); }); setupListeners(); } return { init, setTargets }; })(); var updater = (() => { function showBanner(info) { var banner = document.getElementById('update-banner'); var msg = document.getElementById('update-message'); var link = document.getElementById('update-link'); var text = 'Argus v' + info.version + ' is available.'; msg.textContent = text; link.href = info.url; link.addEventListener('click', function (e) { e.preventDefault(); invoke('launch_url', { url: info.url }); }); banner.classList.add('visible'); } async function check() { try { var update = await invoke('check_for_update'); if (update) showBanner(update); } catch (err) { console.log('Update check skipped:', err); } } function init() { document.getElementById('update-dismiss').addEventListener('click', function () { document.getElementById('update-banner').classList.remove('visible'); }); // Check on startup after a short delay setTimeout(check, 3000); } return { init }; })(); var changelog = (() => { var modal = function () { return document.getElementById('changelog-modal'); }; var loaded = false; async function open() { modal().classList.add('visible'); if (!loaded) { try { var html = await invoke('get_changelog'); document.getElementById('changelog-content').innerHTML = html; loaded = true; } catch (err) { document.getElementById('changelog-content').textContent = 'Failed to load changelog.'; } } } function close() { modal().classList.remove('visible'); } function init() { document.getElementById('btn-changelog').addEventListener('click', open); document.getElementById('changelog-close').addEventListener('click', close); modal().addEventListener('click', function (e) { if (e.target === modal()) close(); }); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && modal().classList.contains('visible')) close(); }); } return { init }; })(); window.addEventListener('DOMContentLoaded', () => { // Tab switching document.querySelectorAll('.tab').forEach((tab) => { tab.addEventListener('click', () => { document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active')); document.querySelectorAll('.view').forEach((v) => v.classList.remove('active')); tab.classList.add('active'); const view = document.getElementById('view-' + tab.dataset.tab); if (view) view.classList.add('active'); }); }); // Sort on header click document.querySelectorAll('#results-table th.sortable').forEach((th) => { th.addEventListener('click', () => table.setSort(th.dataset.sort)); }); // Filter input document.getElementById('filter-input').addEventListener('input', (e) => { table.setFilter(e.target.value); }); // Boot scanner table.init(); scanner.init(); ctxMenu.init(); traceroute.init(); columns.init(); sender.init(); nmapTab.init(); monitorTab.init(); updater.init(); changelog.init(); theme.init(); document.getElementById('btn-export-all').addEventListener('click', exporter.csvAll); document.getElementById('btn-export-up').addEventListener('click', exporter.csvUp); console.log('[Argus] Ready'); // Set version from Tauri config if (window.__TAURI__ && window.__TAURI__.app) { window.__TAURI__.app.getVersion().then(function (version) { document.getElementById('status-bar-version').textContent = 'Argus v' + version; }); } // Check privilege status invoke('check_privileges').then(function (elevated) { var el = document.getElementById('privilege-status'); if (elevated) { el.textContent = 'elevated'; el.classList.add('privilege-elevated'); } else { el.textContent = 'unprivileged'; } }); });