/* AUTARCH Web UI - Vanilla JS */ // Auto-dismiss flash messages after 5s document.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.flash').forEach(function(el) { setTimeout(function() { el.style.opacity = '0'; setTimeout(function() { el.remove(); }, 300); }, 5000); }); }); // ==================== HELPERS ==================== function escapeHtml(str) { var div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function fetchJSON(url, options) { options = options || {}; options.headers = options.headers || {}; options.headers['Accept'] = 'application/json'; return fetch(url, options).then(function(resp) { if (resp.status === 401 || resp.status === 302) { window.location.href = '/auth/login'; throw new Error('Unauthorized'); } return resp.json(); }); } function postJSON(url, body) { return fetchJSON(url, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body) }); } function renderOutput(elementId, text) { var el = document.getElementById(elementId); if (el) el.textContent = text; } /* ── Refresh Modules ─────────────────────────────────────────── */ function reloadModules() { var btn = document.getElementById('btn-reload-modules'); if (!btn) return; var origText = btn.innerHTML; btn.innerHTML = '↻ Reloading...'; btn.disabled = true; btn.style.color = 'var(--accent)'; postJSON('/api/modules/reload', {}).then(function(data) { var total = data.total || 0; btn.innerHTML = '✓ ' + total + ' modules loaded'; btn.style.color = 'var(--success)'; // If on a category page, reload to reflect changes var path = window.location.pathname; var isCategoryPage = /^\/(defense|offense|counter|analyze|osint|simulate)\/?$/.test(path) || path === '/'; if (isCategoryPage) { setTimeout(function() { window.location.reload(); }, 800); } else { setTimeout(function() { btn.innerHTML = origText; btn.style.color = 'var(--text-secondary)'; btn.disabled = false; }, 2000); } }).catch(function() { btn.innerHTML = '✗ Reload failed'; btn.style.color = 'var(--danger)'; setTimeout(function() { btn.innerHTML = origText; btn.style.color = 'var(--text-secondary)'; btn.disabled = false; }, 2000); }); } function showTab(tabGroup, tabId) { document.querySelectorAll('[data-tab-group="' + tabGroup + '"].tab').forEach(function(t) { t.classList.toggle('active', t.dataset.tab === tabId); }); document.querySelectorAll('[data-tab-group="' + tabGroup + '"].tab-content').forEach(function(c) { c.classList.toggle('active', c.dataset.tab === tabId); }); } function setLoading(btn, loading) { if (loading) { btn.dataset.origText = btn.textContent; btn.textContent = 'Loading...'; btn.disabled = true; } else { btn.textContent = btn.dataset.origText || btn.textContent; btn.disabled = false; } } // ==================== OSINT ==================== var osintEventSource = null; var osintResults = []; var currentDossierId = null; function getSelectedCategories() { var boxes = document.querySelectorAll('.cat-checkbox'); if (boxes.length === 0) return ''; var allChecked = document.getElementById('cat-all'); if (allChecked && allChecked.checked) return ''; var selected = []; boxes.forEach(function(cb) { if (cb.checked) selected.push(cb.value); }); return selected.join(','); } function toggleAllCategories(checked) { document.querySelectorAll('.cat-checkbox').forEach(function(cb) { cb.checked = checked; }); } function toggleAdvanced() { var body = document.getElementById('advanced-body'); var arrow = document.getElementById('adv-arrow'); body.classList.toggle('visible'); arrow.classList.toggle('open'); } function stopOsintSearch() { if (osintEventSource) { osintEventSource.close(); osintEventSource = null; } var searchBtn = document.getElementById('search-btn'); var stopBtn = document.getElementById('stop-btn'); searchBtn.disabled = false; searchBtn.textContent = 'Search'; stopBtn.style.display = 'none'; document.getElementById('progress-text').textContent = 'Search stopped.'; } function startOsintSearch() { var query = document.getElementById('osint-query').value.trim(); if (!query) return; var type = document.getElementById('osint-type').value; var maxSites = document.getElementById('osint-max').value; var nsfw = document.getElementById('osint-nsfw') && document.getElementById('osint-nsfw').checked; var categories = getSelectedCategories(); // Advanced options var threads = document.getElementById('osint-threads') ? document.getElementById('osint-threads').value : '8'; var timeout = document.getElementById('osint-timeout') ? document.getElementById('osint-timeout').value : '8'; var ua = document.getElementById('osint-ua') ? document.getElementById('osint-ua').value : ''; var proxy = document.getElementById('osint-proxy') ? document.getElementById('osint-proxy').value.trim() : ''; var resultsDiv = document.getElementById('osint-results'); var progressFill = document.getElementById('progress-fill'); var progressText = document.getElementById('progress-text'); resultsDiv.innerHTML = ''; progressFill.style.width = '0%'; progressText.textContent = 'Starting search...'; osintResults = []; // Show/hide UI elements document.getElementById('results-section').style.display = 'block'; document.getElementById('osint-summary').style.display = 'flex'; document.getElementById('result-actions').style.display = 'none'; document.getElementById('save-dossier-panel').style.display = 'none'; document.getElementById('sum-checked').textContent = '0'; document.getElementById('sum-found').textContent = '0'; document.getElementById('sum-maybe').textContent = '0'; document.getElementById('sum-filtered').textContent = '0'; var searchBtn = document.getElementById('search-btn'); var stopBtn = document.getElementById('stop-btn'); searchBtn.disabled = true; searchBtn.textContent = 'Searching...'; stopBtn.style.display = 'inline-block'; var url = '/osint/search/stream?type=' + type + '&q=' + encodeURIComponent(query) + '&max=' + maxSites + '&nsfw=' + (nsfw ? 'true' : 'false') + '&categories=' + encodeURIComponent(categories) + '&threads=' + threads + '&timeout=' + timeout; if (ua) url += '&ua=' + encodeURIComponent(ua); if (proxy) url += '&proxy=' + encodeURIComponent(proxy); var source = new EventSource(url); osintEventSource = source; var foundCount = 0; var maybeCount = 0; var filteredCount = 0; source.onmessage = function(e) { var data = JSON.parse(e.data); if (data.type === 'start') { progressText.textContent = 'Checking ' + data.total + ' sites...'; } else if (data.type === 'result') { var pct = ((data.checked / data.total) * 100).toFixed(1); progressFill.style.width = pct + '%'; progressText.textContent = data.checked + ' / ' + data.total + ' checked'; document.getElementById('sum-checked').textContent = data.checked; if (data.status === 'good') { foundCount++; document.getElementById('sum-found').textContent = foundCount; osintResults.push(data); var card = document.createElement('div'); card.className = 'result-card found'; card.dataset.status = 'good'; card.onclick = function() { toggleResultDetail(card); }; var conf = data.rate || 0; var confClass = conf >= 70 ? 'high' : conf >= 50 ? 'medium' : 'low'; card.innerHTML = '
' + escapeHtml(data.site) + ' ' + '' + conf + '%' + '' + '' + escapeHtml(data.category || '') + '' + '
' + '
URL: ' + escapeHtml(data.url || '') + '
' + (data.title ? '
Title: ' + escapeHtml(data.title) + '
' : '') + '
Method: ' + escapeHtml(data.method || '') + ' | HTTP ' + (data.http_code || '') + '
' + '
' + 'Open'; resultsDiv.prepend(card); } else if (data.status === 'maybe') { maybeCount++; document.getElementById('sum-maybe').textContent = maybeCount; osintResults.push(data); var card = document.createElement('div'); card.className = 'result-card maybe'; card.dataset.status = 'maybe'; card.onclick = function() { toggleResultDetail(card); }; var conf = data.rate || 0; var confClass = conf >= 50 ? 'medium' : 'low'; card.innerHTML = '
' + escapeHtml(data.site) + ' ' + '' + conf + '%' + '' + '' + escapeHtml(data.category || '') + '' + '
' + '
URL: ' + escapeHtml(data.url || '') + '
' + (data.title ? '
Title: ' + escapeHtml(data.title) + '
' : '') + '
Method: ' + escapeHtml(data.method || '') + ' | HTTP ' + (data.http_code || '') + '
' + '
' + 'Open'; resultsDiv.appendChild(card); } else if (data.status === 'filtered') { filteredCount++; document.getElementById('sum-filtered').textContent = filteredCount; var card = document.createElement('div'); card.className = 'result-card filtered'; card.dataset.status = 'filtered'; card.innerHTML = '' + escapeHtml(data.site) + 'WAF/Filtered'; resultsDiv.appendChild(card); } } else if (data.type === 'done') { progressFill.style.width = '100%'; progressText.textContent = 'Done: ' + data.checked + ' checked, ' + data.found + ' found, ' + (data.maybe || 0) + ' possible'; source.close(); osintEventSource = null; searchBtn.disabled = false; searchBtn.textContent = 'Search'; stopBtn.style.display = 'none'; if (osintResults.length > 0) { document.getElementById('result-actions').style.display = 'flex'; } } else if (data.type === 'error' || data.error) { progressText.textContent = 'Error: ' + (data.message || data.error); source.close(); osintEventSource = null; searchBtn.disabled = false; searchBtn.textContent = 'Search'; stopBtn.style.display = 'none'; } }; source.onerror = function() { source.close(); osintEventSource = null; searchBtn.disabled = false; searchBtn.textContent = 'Search'; stopBtn.style.display = 'none'; progressText.textContent = 'Connection lost'; }; } function toggleResultDetail(card) { var detail = card.querySelector('.result-detail'); if (detail) detail.classList.toggle('visible'); } function filterResults(filter) { document.querySelectorAll('.result-filters .filter-btn').forEach(function(b) { b.classList.toggle('active', b.dataset.filter === filter); }); document.querySelectorAll('#osint-results .result-card').forEach(function(card) { if (filter === 'all') { card.style.display = ''; } else { card.style.display = card.dataset.status === filter ? '' : 'none'; } }); } function openAllFound() { osintResults.forEach(function(r) { if (r.status === 'good' && r.url) { window.open(r.url, '_blank'); } }); } function exportResults(fmt) { var query = document.getElementById('osint-query').value.trim(); postJSON('/osint/export', { results: osintResults, format: fmt, query: query }).then(function(data) { if (data.error) { alert('Export error: ' + data.error); return; } alert('Exported to: ' + data.path); }); } function showSaveToDossier() { var panel = document.getElementById('save-dossier-panel'); panel.style.display = 'block'; document.getElementById('dossier-save-status').textContent = ''; // Load existing dossiers for selection fetchJSON('/osint/dossiers').then(function(data) { var list = document.getElementById('dossier-select-list'); var dossiers = data.dossiers || []; if (dossiers.length === 0) { list.innerHTML = '
No existing dossiers. Create one below.
'; return; } var html = ''; dossiers.forEach(function(d) { html += ''; }); list.innerHTML = html; }); } function saveToDossier(dossierId) { postJSON('/osint/dossier/' + dossierId + '/add', {results: osintResults}).then(function(data) { var status = document.getElementById('dossier-save-status'); if (data.error) { status.textContent = 'Error: ' + data.error; return; } status.textContent = 'Added ' + data.added + ' results (total: ' + data.total + ')'; status.style.color = 'var(--success)'; }); } function createAndSaveDossier() { var name = document.getElementById('new-dossier-name').value.trim(); if (!name) return; var query = document.getElementById('osint-query').value.trim(); postJSON('/osint/dossier', {name: name, target: query}).then(function(data) { if (data.error) { document.getElementById('dossier-save-status').textContent = 'Error: ' + data.error; return; } document.getElementById('new-dossier-name').value = ''; saveToDossier(data.dossier.id); }); } // ==================== DOSSIER MANAGEMENT ==================== function loadDossiers() { fetchJSON('/osint/dossiers').then(function(data) { var container = document.getElementById('dossier-list'); var dossiers = data.dossiers || []; if (dossiers.length === 0) { container.innerHTML = '
No dossiers yet. Run a search and save results.
'; return; } var html = ''; dossiers.forEach(function(d) { html += '
' + '

' + escapeHtml(d.name) + '

' + '
' + escapeHtml(d.target || '') + ' | ' + escapeHtml(d.created ? d.created.split('T')[0] : '') + '
' + '
' + d.result_count + ' results
' + '
'; }); container.innerHTML = html; }); } function viewDossier(dossierId) { currentDossierId = dossierId; fetchJSON('/osint/dossier/' + dossierId).then(function(data) { if (data.error) { alert(data.error); return; } var d = data.dossier; document.getElementById('dossier-detail').style.display = 'block'; document.getElementById('dossier-detail-name').textContent = d.name + (d.target ? ' - ' + d.target : ''); document.getElementById('dossier-notes').value = d.notes || ''; var results = d.results || []; var container = document.getElementById('dossier-results-list'); if (results.length === 0) { container.innerHTML = '
No results in this dossier.
'; return; } var html = ''; results.forEach(function(r) { var badgeCls = r.status === 'good' ? 'badge-pass' : r.status === 'maybe' ? 'badge-medium' : 'badge-info'; html += '
' + '
' + escapeHtml(r.name) + ' ' + '' + (r.rate || 0) + '%' + '' + escapeHtml(r.category || '') + '
' + 'Open' + '
'; }); container.innerHTML = html; }); } function saveDossierNotes() { if (!currentDossierId) return; var notes = document.getElementById('dossier-notes').value; fetchJSON('/osint/dossier/' + currentDossierId, { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({notes: notes}) }).then(function(data) { if (data.success) alert('Notes saved.'); }); } function deleteDossier() { if (!currentDossierId || !confirm('Delete this dossier?')) return; fetchJSON('/osint/dossier/' + currentDossierId, {method: 'DELETE'}).then(function(data) { if (data.success) { closeDossierDetail(); loadDossiers(); } }); } function closeDossierDetail() { document.getElementById('dossier-detail').style.display = 'none'; currentDossierId = null; } function exportDossier(fmt) { if (!currentDossierId) return; fetchJSON('/osint/dossier/' + currentDossierId).then(function(data) { if (data.error) return; var d = data.dossier; postJSON('/osint/export', { results: d.results || [], format: fmt, query: d.target || d.name }).then(function(exp) { if (exp.error) { alert('Export error: ' + exp.error); return; } alert('Exported to: ' + exp.path); }); }); } // ==================== DEFENSE ==================== function runDefenseAudit() { var btn = document.getElementById('btn-audit'); setLoading(btn, true); postJSON('/defense/audit', {}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('audit-output', 'Error: ' + data.error); return; } // Score var scoreEl = document.getElementById('audit-score'); if (scoreEl) { scoreEl.textContent = data.score + '%'; scoreEl.style.color = data.score >= 80 ? 'var(--success)' : data.score >= 50 ? 'var(--warning)' : 'var(--danger)'; } // Checks table var html = ''; (data.checks || []).forEach(function(c) { html += '' + escapeHtml(c.name) + '' + (c.passed ? 'PASS' : 'FAIL') + '' + escapeHtml(c.details || '') + ''; }); document.getElementById('audit-results').innerHTML = html; }).catch(function() { setLoading(btn, false); }); } function runDefenseCheck(name) { var resultEl = document.getElementById('check-result-' + name); if (resultEl) { resultEl.textContent = 'Running...'; resultEl.style.display = 'block'; } postJSON('/defense/check/' + name, {}).then(function(data) { if (data.error) { if (resultEl) resultEl.textContent = 'Error: ' + data.error; return; } var lines = (data.checks || []).map(function(c) { return (c.passed ? '[PASS] ' : '[FAIL] ') + c.name + (c.details ? ' - ' + c.details : ''); }); if (resultEl) resultEl.textContent = lines.join('\n') || 'No results'; }).catch(function() { if (resultEl) resultEl.textContent = 'Request failed'; }); } function loadFirewallRules() { fetchJSON('/defense/firewall/rules').then(function(data) { renderOutput('fw-rules', data.rules || 'Could not load rules'); }); } function blockIP() { var ip = document.getElementById('block-ip').value.trim(); if (!ip) return; postJSON('/defense/firewall/block', {ip: ip}).then(function(data) { renderOutput('fw-result', data.message || data.error); if (data.success) { document.getElementById('block-ip').value = ''; loadFirewallRules(); } }); } function unblockIP(ip) { postJSON('/defense/firewall/unblock', {ip: ip}).then(function(data) { renderOutput('fw-result', data.message || data.error); if (data.success) loadFirewallRules(); }); } function analyzeLogs() { var btn = document.getElementById('btn-logs'); setLoading(btn, true); postJSON('/defense/logs/analyze', {}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('log-output', 'Error: ' + data.error); return; } var lines = []; if (data.auth_results && data.auth_results.length) { lines.push('=== Auth Log Analysis ==='); data.auth_results.forEach(function(r) { lines.push(r.ip + ': ' + r.count + ' failures (' + (r.usernames||[]).join(', ') + ')'); }); } if (data.web_results && data.web_results.length) { lines.push('\n=== Web Log Findings ==='); data.web_results.forEach(function(r) { lines.push('[' + r.severity + '] ' + r.type + ' from ' + r.ip + ' - ' + (r.detail||'')); }); } renderOutput('log-output', lines.join('\n') || 'No findings'); }).catch(function() { setLoading(btn, false); }); } // ==================== COUNTER ==================== function runCounterScan() { var btn = document.getElementById('btn-scan'); setLoading(btn, true); postJSON('/counter/scan', {}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('scan-output', 'Error: ' + data.error); return; } var container = document.getElementById('scan-results'); var html = ''; var threats = data.threats || []; if (threats.length === 0) { html = '
No threats detected.
'; } else { threats.forEach(function(t) { var cls = t.severity === 'high' ? 'badge-high' : t.severity === 'medium' ? 'badge-medium' : 'badge-low'; html += '
' + escapeHtml(t.severity).toUpperCase() + '
' + escapeHtml(t.message) + '
' + escapeHtml(t.category) + '
'; }); } container.innerHTML = html; // Summary var sumEl = document.getElementById('scan-summary'); if (sumEl && data.summary) sumEl.textContent = data.summary; }).catch(function() { setLoading(btn, false); }); } function runCounterCheck(name) { var resultEl = document.getElementById('counter-result-' + name); if (resultEl) { resultEl.textContent = 'Running...'; resultEl.style.display = 'block'; } postJSON('/counter/check/' + name, {}).then(function(data) { if (data.error) { if (resultEl) resultEl.textContent = 'Error: ' + data.error; return; } var lines = (data.threats || []).map(function(t) { return '[' + t.severity.toUpperCase() + '] ' + t.category + ': ' + t.message; }); if (resultEl) resultEl.textContent = lines.join('\n') || data.message || 'No threats found'; }).catch(function() { if (resultEl) resultEl.textContent = 'Request failed'; }); } function loadLogins() { var btn = document.getElementById('btn-logins'); setLoading(btn, true); fetchJSON('/counter/logins').then(function(data) { setLoading(btn, false); var container = document.getElementById('login-results'); if (data.error) { container.innerHTML = '
' + escapeHtml(data.error) + '
'; return; } var attempts = data.attempts || []; if (attempts.length === 0) { container.innerHTML = '
No failed login attempts found.
'; return; } var html = ''; attempts.forEach(function(a) { html += ''; }); html += '
IPAttemptsUsernamesCountryISP
' + escapeHtml(a.ip) + '' + a.count + '' + escapeHtml((a.usernames||[]).join(', ')) + '' + escapeHtml(a.country||'-') + '' + escapeHtml(a.isp||'-') + '
'; container.innerHTML = html; }).catch(function() { setLoading(btn, false); }); } // ==================== ANALYZE ==================== function analyzeFile() { var filepath = document.getElementById('analyze-filepath').value.trim(); if (!filepath) return; var btn = document.getElementById('btn-analyze-file'); setLoading(btn, true); postJSON('/analyze/file', {filepath: filepath}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('file-output', 'Error: ' + data.error); return; } var lines = []; lines.push('Path: ' + (data.path || '')); lines.push('Size: ' + (data.size || 0) + ' bytes'); lines.push('Modified: ' + (data.modified || '')); lines.push('MIME: ' + (data.mime || '')); lines.push('Type: ' + (data.type || '')); if (data.hashes) { lines.push('\nHashes:'); lines.push(' MD5: ' + (data.hashes.md5 || '')); lines.push(' SHA1: ' + (data.hashes.sha1 || '')); lines.push(' SHA256: ' + (data.hashes.sha256 || '')); } renderOutput('file-output', lines.join('\n')); }).catch(function() { setLoading(btn, false); }); } function extractStrings() { var filepath = document.getElementById('strings-filepath').value.trim(); var minLen = document.getElementById('strings-minlen').value || '4'; if (!filepath) return; var btn = document.getElementById('btn-strings'); setLoading(btn, true); postJSON('/analyze/strings', {filepath: filepath, min_len: parseInt(minLen)}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('strings-output', 'Error: ' + data.error); return; } var lines = []; if (data.urls && data.urls.length) { lines.push('URLs (' + data.urls.length + '):'); data.urls.forEach(function(u){lines.push(' ' + u);}); lines.push(''); } if (data.ips && data.ips.length) { lines.push('IPs (' + data.ips.length + '):'); data.ips.forEach(function(i){lines.push(' ' + i);}); lines.push(''); } if (data.emails && data.emails.length) { lines.push('Emails (' + data.emails.length + '):'); data.emails.forEach(function(e){lines.push(' ' + e);}); lines.push(''); } if (data.paths && data.paths.length) { lines.push('Paths (' + data.paths.length + '):'); data.paths.forEach(function(p){lines.push(' ' + p);}); } renderOutput('strings-output', lines.join('\n') || 'No interesting strings found'); }).catch(function() { setLoading(btn, false); }); } function hashLookup() { var hash = document.getElementById('hash-input').value.trim(); if (!hash) return; postJSON('/analyze/hash', {hash: hash}).then(function(data) { if (data.error) { renderOutput('hash-output', 'Error: ' + data.error); return; } var lines = ['Hash Type: ' + (data.hash_type || 'Unknown'), '']; (data.links || []).forEach(function(l) { lines.push(l.name + ': ' + l.url); }); renderOutput('hash-output', lines.join('\n')); }); } function analyzeLog() { var filepath = document.getElementById('log-filepath').value.trim(); if (!filepath) return; var btn = document.getElementById('btn-analyze-log'); setLoading(btn, true); postJSON('/analyze/log', {filepath: filepath}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('log-analyze-output', 'Error: ' + data.error); return; } var lines = ['Total lines: ' + (data.total_lines || 0), '']; if (data.ip_counts && data.ip_counts.length) { lines.push('Top IPs:'); data.ip_counts.forEach(function(i) { lines.push(' ' + i[0] + ': ' + i[1] + ' occurrences'); }); lines.push(''); } lines.push('Errors: ' + (data.error_count || 0)); if (data.time_range) { lines.push('Time Range: ' + data.time_range.first + ' - ' + data.time_range.last); } renderOutput('log-analyze-output', lines.join('\n')); }).catch(function() { setLoading(btn, false); }); } function hexDump() { var filepath = document.getElementById('hex-filepath').value.trim(); var offset = document.getElementById('hex-offset').value || '0'; var length = document.getElementById('hex-length').value || '256'; if (!filepath) return; var btn = document.getElementById('btn-hex'); setLoading(btn, true); postJSON('/analyze/hex', {filepath: filepath, offset: parseInt(offset), length: parseInt(length)}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('hex-output', 'Error: ' + data.error); return; } renderOutput('hex-output', data.hex || 'No data'); }).catch(function() { setLoading(btn, false); }); } function compareFiles() { var file1 = document.getElementById('compare-file1').value.trim(); var file2 = document.getElementById('compare-file2').value.trim(); if (!file1 || !file2) return; var btn = document.getElementById('btn-compare'); setLoading(btn, true); postJSON('/analyze/compare', {file1: file1, file2: file2}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('compare-output', 'Error: ' + data.error); return; } var lines = []; lines.push('File 1: ' + data.file1_size + ' bytes'); lines.push('File 2: ' + data.file2_size + ' bytes'); lines.push('Difference: ' + data.size_diff + ' bytes'); lines.push(''); lines.push('MD5: ' + (data.md5_match ? 'MATCH' : 'DIFFERENT')); lines.push('SHA256: ' + (data.sha256_match ? 'MATCH' : 'DIFFERENT')); if (data.diff) { lines.push('\nDiff:\n' + data.diff); } renderOutput('compare-output', lines.join('\n')); }).catch(function() { setLoading(btn, false); }); } // ==================== SIMULATE ==================== function auditPassword() { var pw = document.getElementById('sim-password').value; if (!pw) return; var btn = document.getElementById('btn-password'); setLoading(btn, true); postJSON('/simulate/password', {password: pw}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('password-output', 'Error: ' + data.error); return; } // Score var scoreEl = document.getElementById('pw-score'); if (scoreEl) { scoreEl.textContent = data.score + '/10'; scoreEl.style.color = data.score >= 8 ? 'var(--success)' : data.score >= 5 ? 'var(--warning)' : 'var(--danger)'; } var strengthEl = document.getElementById('pw-strength'); if (strengthEl) strengthEl.textContent = data.strength || ''; // Feedback var lines = (data.feedback || []).map(function(f) { return f; }); if (data.hashes) { lines.push(''); lines.push('MD5: ' + data.hashes.md5); lines.push('SHA1: ' + data.hashes.sha1); lines.push('SHA256: ' + data.hashes.sha256); } renderOutput('password-output', lines.join('\n')); }).catch(function() { setLoading(btn, false); }); } function scanPorts() { var target = document.getElementById('scan-target').value.trim(); var ports = document.getElementById('scan-ports').value.trim() || '1-1024'; if (!target) return; var btn = document.getElementById('btn-portscan'); setLoading(btn, true); document.getElementById('portscan-output').textContent = 'Scanning... this may take a while.'; postJSON('/simulate/portscan', {target: target, ports: ports}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('portscan-output', 'Error: ' + data.error); return; } var lines = []; var ports = data.open_ports || []; if (ports.length) { lines.push('Open ports on ' + escapeHtml(target) + ':'); lines.push(''); ports.forEach(function(p) { lines.push(' ' + p.port + '/tcp open ' + (p.service || 'unknown')); }); } else { lines.push('No open ports found'); } lines.push('\nScanned: ' + (data.scanned || 0) + ' ports'); renderOutput('portscan-output', lines.join('\n')); }).catch(function() { setLoading(btn, false); }); } function grabBanner() { var target = document.getElementById('banner-target').value.trim(); var port = document.getElementById('banner-port').value.trim() || '80'; if (!target) return; var btn = document.getElementById('btn-banner'); setLoading(btn, true); postJSON('/simulate/banner', {target: target, port: parseInt(port)}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('banner-output', 'Error: ' + data.error); return; } renderOutput('banner-output', data.banner || 'No banner received'); }).catch(function() { setLoading(btn, false); }); } function generatePayloads() { var type = document.getElementById('payload-type').value; var btn = document.getElementById('btn-payloads'); setLoading(btn, true); postJSON('/simulate/payloads', {type: type}).then(function(data) { setLoading(btn, false); if (data.error) { renderOutput('payload-output', 'Error: ' + data.error); return; } var container = document.getElementById('payload-list'); var html = ''; (data.payloads || []).forEach(function(p) { html += '
' + escapeHtml(p) + '
'; }); container.innerHTML = html; }).catch(function() { setLoading(btn, false); }); } // ==================== OFFENSE ==================== function checkMSFStatus() { fetchJSON('/offense/status').then(function(data) { var el = document.getElementById('msf-status'); if (!el) return; var dot = data.connected ? '' : ''; var text = data.connected ? 'Connected' : 'Disconnected'; el.innerHTML = dot + text; if (data.connected) { var info = document.getElementById('msf-info'); if (info) info.textContent = (data.host || '') + ':' + (data.port || '') + (data.version ? ' (v' + data.version + ')' : ''); } }).catch(function() { var el = document.getElementById('msf-status'); if (el) el.innerHTML = 'Disconnected'; }); } function searchMSFModules() { var query = document.getElementById('msf-search').value.trim(); if (!query) return; var btn = document.getElementById('btn-msf-search'); setLoading(btn, true); postJSON('/offense/search', {query: query}).then(function(data) { setLoading(btn, false); var container = document.getElementById('msf-search-results'); if (data.error) { container.innerHTML = '
' + escapeHtml(data.error) + '
'; return; } var modules = data.modules || []; if (modules.length === 0) { container.innerHTML = '
No modules found.
'; return; } var html = ''; modules.forEach(function(m) { var typeBadge = 'badge-info'; if (m.path && m.path.startsWith('exploit')) typeBadge = 'badge-high'; else if (m.path && m.path.startsWith('auxiliary')) typeBadge = 'badge-medium'; var type = m.path ? m.path.split('/')[0] : ''; html += '
' + escapeHtml(m.name || m.path) + ' ' + escapeHtml(type) + '' + '
' + escapeHtml(m.path || '') + '
'; }); container.innerHTML = html; }).catch(function() { setLoading(btn, false); }); } function loadMSFSessions() { fetchJSON('/offense/sessions').then(function(data) { var container = document.getElementById('msf-sessions'); if (data.error) { container.innerHTML = '
' + escapeHtml(data.error) + '
'; return; } var sessions = data.sessions || {}; var keys = Object.keys(sessions); if (keys.length === 0) { container.innerHTML = '
No active sessions.
'; return; } var html = ''; keys.forEach(function(id) { var s = sessions[id]; html += ''; }); html += '
IDTypeTargetInfo
' + escapeHtml(id) + '' + escapeHtml(s.type || '') + '' + escapeHtml(s.tunnel_peer || s.target_host || '') + '' + escapeHtml(s.info || '') + '
'; container.innerHTML = html; }); } function browseMSFModules(type) { var page = parseInt(document.getElementById('msf-page-' + type)?.value || '1'); fetchJSON('/offense/modules/' + type + '?page=' + page).then(function(data) { var container = document.getElementById('msf-modules-' + type); if (data.error) { container.innerHTML = '
' + escapeHtml(data.error) + '
'; return; } var modules = data.modules || []; if (modules.length === 0) { container.innerHTML = '
No modules in this category.
'; return; } var html = ''; modules.forEach(function(m) { html += '
' + '' + escapeHtml(m.name || '') + '' + '
' + escapeHtml(m.path || '') + '
' + '
'; }); if (data.has_more) { html += '
'; } container.innerHTML = html; }); } // ==================== WIRESHARK ==================== var wsEventSource = null; function wsLoadInterfaces() { var sel = document.getElementById('ws-interface'); if (!sel) return; fetchJSON('/wireshark/interfaces').then(function(data) { var ifaces = data.interfaces || []; sel.innerHTML = ''; ifaces.forEach(function(i) { var desc = i.description ? ' (' + i.description + ')' : ''; sel.innerHTML += ''; }); }).catch(function() { sel.innerHTML = ''; }); } function wsStartCapture() { var iface = document.getElementById('ws-interface').value; var filter = document.getElementById('ws-filter').value.trim(); var duration = parseInt(document.getElementById('ws-duration').value) || 30; var btn = document.getElementById('btn-ws-start'); var stopBtn = document.getElementById('btn-ws-stop'); setLoading(btn, true); stopBtn.style.display = 'inline-block'; postJSON('/wireshark/capture/start', { interface: iface, filter: filter, duration: duration }).then(function(data) { if (data.error) { setLoading(btn, false); stopBtn.style.display = 'none'; document.getElementById('ws-progress').textContent = 'Error: ' + data.error; return; } document.getElementById('ws-progress').textContent = 'Capturing...'; document.getElementById('ws-capture-status').innerHTML = 'Capturing'; // Start SSE stream var liveDiv = document.getElementById('ws-live-packets'); liveDiv.style.display = 'block'; liveDiv.textContent = ''; wsEventSource = new EventSource('/wireshark/capture/stream'); var pktCount = 0; wsEventSource.onmessage = function(e) { var d = JSON.parse(e.data); if (d.type === 'packet') { pktCount++; var line = (d.src || '?') + ' -> ' + (d.dst || '?') + ' ' + (d.protocol || '') + ' ' + (d.info || ''); liveDiv.textContent += line + '\n'; liveDiv.scrollTop = liveDiv.scrollHeight; document.getElementById('ws-progress').textContent = pktCount + ' packets captured'; } else if (d.type === 'done') { wsEventSource.close(); wsEventSource = null; setLoading(btn, false); stopBtn.style.display = 'none'; document.getElementById('ws-progress').textContent = 'Capture complete: ' + (d.packet_count || pktCount) + ' packets'; document.getElementById('ws-capture-status').innerHTML = 'Idle'; document.getElementById('ws-analysis-section').style.display = 'block'; wsShowPacketsTable(); } }; wsEventSource.onerror = function() { wsEventSource.close(); wsEventSource = null; setLoading(btn, false); stopBtn.style.display = 'none'; document.getElementById('ws-capture-status').innerHTML = 'Idle'; }; }).catch(function() { setLoading(btn, false); stopBtn.style.display = 'none'; }); } function wsStopCapture() { postJSON('/wireshark/capture/stop', {}).then(function(data) { document.getElementById('ws-progress').textContent = 'Stopping...'; }); if (wsEventSource) { wsEventSource.close(); wsEventSource = null; } } function wsAnalyzePcap() { var filepath = document.getElementById('ws-pcap-path').value.trim(); if (!filepath) return; var btn = document.getElementById('btn-ws-pcap'); setLoading(btn, true); document.getElementById('ws-pcap-info').textContent = 'Loading...'; postJSON('/wireshark/pcap/analyze', {filepath: filepath}).then(function(data) { setLoading(btn, false); if (data.error) { document.getElementById('ws-pcap-info').textContent = 'Error: ' + data.error; return; } var info = data.total_packets + ' packets loaded'; if (data.size) info += ' (' + Math.round(data.size/1024) + ' KB)'; if (data.truncated) info += ' (showing first 500)'; document.getElementById('ws-pcap-info').textContent = info; document.getElementById('ws-analysis-section').style.display = 'block'; wsRenderPackets(data.packets || []); }).catch(function() { setLoading(btn, false); }); } function wsShowPacketsTable() { fetchJSON('/wireshark/capture/stats').then(function(stats) { document.getElementById('ws-packets-table').textContent = stats.packet_count + ' packets captured. Use analysis tabs to explore.'; }); } function wsRenderPackets(packets) { var container = document.getElementById('ws-packets-table'); if (!packets.length) { container.textContent = 'No packets to display.'; return; } var lines = []; lines.push('No. Source Destination Proto Length Info'); lines.push('─'.repeat(90)); packets.forEach(function(p, i) { var num = String(i+1).padEnd(6); var src = (p.src || '').padEnd(21); var dst = (p.dst || '').padEnd(21); var proto = (p.protocol || '').padEnd(9); var len = String(p.length || 0).padEnd(8); var info = (p.info || '').substring(0, 40); lines.push(num + src + dst + proto + len + info); }); container.textContent = lines.join('\n'); } function wsLoadProtocols() { var container = document.getElementById('ws-protocols'); container.innerHTML = '
Loading...
'; postJSON('/wireshark/analyze/protocols', {}).then(function(data) { var protocols = data.protocols || {}; var keys = Object.keys(protocols); if (keys.length === 0) { container.innerHTML = '
No protocol data.
'; return; } var html = '
Total: ' + (data.total || 0) + ' packets
'; keys.forEach(function(proto) { var d = protocols[proto]; var barWidth = Math.max(2, d.percent); html += '
' + '' + escapeHtml(proto) + '' + '
' + '
' + '' + d.count + ' (' + d.percent + '%)' + '
'; }); container.innerHTML = html; }); } function wsLoadConversations() { var container = document.getElementById('ws-conversations'); container.innerHTML = '
Loading...
'; postJSON('/wireshark/analyze/conversations', {}).then(function(data) { var convos = data.conversations || []; if (convos.length === 0) { container.innerHTML = '
No conversations found.
'; return; } var html = ''; convos.forEach(function(c) { html += ''; }); html += '
SourceDestinationPacketsBytesProtocols
' + escapeHtml(c.src) + '' + escapeHtml(c.dst) + '' + c.packets + '' + c.bytes + '' + escapeHtml((c.protocols||[]).join(', ')) + '
'; container.innerHTML = html; }); } function wsLoadDNS() { var container = document.getElementById('ws-dns'); container.innerHTML = '
Loading...
'; postJSON('/wireshark/analyze/dns', {}).then(function(data) { var queries = data.queries || []; if (queries.length === 0) { container.innerHTML = '
No DNS queries found.
'; return; } var html = ''; queries.forEach(function(q) { html += ''; }); html += '
QueryTypeCountResponseSource
' + escapeHtml(q.query) + '' + escapeHtml(q.type) + '' + q.count + '' + escapeHtml(q.response || '') + '' + escapeHtml(q.src || '') + '
'; container.innerHTML = html; }); } function wsLoadHTTP() { var container = document.getElementById('ws-http'); container.innerHTML = '
Loading...
'; postJSON('/wireshark/analyze/http', {}).then(function(data) { var reqs = data.requests || []; if (reqs.length === 0) { container.innerHTML = '
No HTTP requests found.
'; return; } var html = ''; reqs.forEach(function(r) { var methodCls = r.method === 'GET' ? 'badge-pass' : r.method === 'POST' ? 'badge-medium' : 'badge-info'; html += '' + '' + ''; }); html += '
MethodHostPathSource
' + escapeHtml(r.method) + '' + escapeHtml(r.host) + '' + escapeHtml((r.path||'').substring(0,60)) + '' + escapeHtml(r.src) + '
'; container.innerHTML = html; }); } function wsLoadCredentials() { var container = document.getElementById('ws-creds'); container.innerHTML = '
Loading...
'; postJSON('/wireshark/analyze/credentials', {}).then(function(data) { var creds = data.credentials || []; if (creds.length === 0) { container.innerHTML = '
No plaintext credentials detected.
'; return; } var html = '
' + creds.length + ' credential artifact(s) found
'; html += ''; creds.forEach(function(c) { html += '' + '' + ''; }); html += '
ProtocolTypeValueSourceDestination
' + escapeHtml(c.protocol) + '' + escapeHtml(c.type) + '' + escapeHtml(c.value) + '' + escapeHtml(c.src) + '' + escapeHtml(c.dst) + '
'; container.innerHTML = html; }); } function wsExport(fmt) { postJSON('/wireshark/export', {format: fmt}).then(function(data) { if (data.error) { alert('Export error: ' + data.error); return; } alert('Exported ' + data.count + ' packets to:\n' + data.filepath); }); } // ==================== HARDWARE ==================== var hwSelectedAdb = ''; var hwSelectedFb = ''; var hwMonitorES = null; var hwConnectionMode = 'server'; // 'server' or 'direct' // ── Mode Switching ── function hwSetMode(mode) { hwConnectionMode = mode; localStorage.setItem('hw_connection_mode', mode); // Toggle button states var serverBtn = document.getElementById('hw-mode-server'); var directBtn = document.getElementById('hw-mode-direct'); if (serverBtn) serverBtn.classList.toggle('active', mode === 'server'); if (directBtn) directBtn.classList.toggle('active', mode === 'direct'); // Toggle status cards var serverStatus = document.getElementById('hw-status-server'); var directStatus = document.getElementById('hw-status-direct'); if (serverStatus) serverStatus.style.display = mode === 'server' ? '' : 'none'; if (directStatus) directStatus.style.display = mode === 'direct' ? '' : 'none'; // Toggle server-only vs direct-only elements var serverEls = ['hw-adb-refresh-bar', 'hw-fb-refresh-bar', 'hw-serial-section', 'hw-sideload-server', 'hw-transfer-server', 'hw-fb-firmware-server', 'hw-esp-flash-server', 'hw-monitor-server']; var directEls = ['hw-direct-adb-connect', 'hw-direct-fb-connect', 'hw-direct-esp-connect', 'hw-sideload-direct', 'hw-transfer-direct', 'hw-fb-firmware-direct', 'hw-esp-flash-direct', 'hw-monitor-direct']; serverEls.forEach(function(id) { var el = document.getElementById(id); if (el) el.style.display = mode === 'server' ? '' : 'none'; }); directEls.forEach(function(id) { var el = document.getElementById(id); if (el) el.style.display = mode === 'direct' ? '' : 'none'; }); // Direct mode: check browser capabilities if (mode === 'direct') { hwCheckDirectCapabilities(); // Hide server device lists in direct mode var adbSection = document.getElementById('hw-adb-section'); if (adbSection) adbSection.style.display = 'none'; var fbSection = document.getElementById('hw-fastboot-section'); if (fbSection) fbSection.style.display = 'none'; } else { var adbSection = document.getElementById('hw-adb-section'); if (adbSection) adbSection.style.display = ''; var fbSection = document.getElementById('hw-fastboot-section'); if (fbSection) fbSection.style.display = ''; hwRefreshAdbDevices(); hwRefreshFastbootDevices(); hwRefreshSerialPorts(); } // Factory flash tab: check mode var factoryWarning = document.getElementById('hw-factory-requires-direct'); var factoryControls = document.getElementById('hw-factory-controls'); if (factoryWarning && factoryControls) { factoryWarning.style.display = mode === 'direct' ? 'none' : 'block'; factoryControls.style.display = mode === 'direct' ? '' : 'none'; } // Warning message var warning = document.getElementById('hw-mode-warning'); if (warning) { if (mode === 'direct' && typeof HWDirect !== 'undefined' && !HWDirect.supported.webusb) { warning.style.display = 'block'; if (typeof window.isSecureContext !== 'undefined' && !window.isSecureContext) { warning.textContent = 'WebUSB requires a secure context (HTTPS). You are accessing over plain HTTP — set [web] https = true in autarch.conf or access via https://. Restart the server after changing config.'; } else { warning.textContent = 'WebUSB is not supported in this browser. Direct mode requires Chrome, Edge, or another Chromium-based browser.'; } } else { warning.style.display = 'none'; } } } function hwCheckDirectCapabilities() { if (typeof HWDirect === 'undefined') return; var usbDot = document.getElementById('hw-cap-webusb'); var usbText = document.getElementById('hw-cap-webusb-text'); var serialDot = document.getElementById('hw-cap-webserial'); var serialText = document.getElementById('hw-cap-webserial-text'); if (usbDot) { usbDot.className = 'status-dot ' + (HWDirect.supported.webusb ? 'active' : 'inactive'); if (usbText) usbText.textContent = HWDirect.supported.webusb ? 'Supported' : 'Not available'; } if (serialDot) { serialDot.className = 'status-dot ' + (HWDirect.supported.webserial ? 'active' : 'inactive'); if (serialText) serialText.textContent = HWDirect.supported.webserial ? 'Supported' : 'Not available'; } hwUpdateDirectStatus(); } function hwUpdateDirectStatus() { if (typeof HWDirect === 'undefined') return; var adbDot = document.getElementById('hw-direct-adb-status'); var adbText = document.getElementById('hw-direct-adb-text'); var fbDot = document.getElementById('hw-direct-fb-status'); var fbText = document.getElementById('hw-direct-fb-text'); if (adbDot) { adbDot.className = 'status-dot ' + (HWDirect.adbIsConnected() ? 'active' : 'inactive'); if (adbText) adbText.textContent = HWDirect.adbIsConnected() ? 'Connected' : 'Not connected'; } if (fbDot) { fbDot.className = 'status-dot ' + (HWDirect.fbIsConnected() ? 'active' : 'inactive'); if (fbText) fbText.textContent = HWDirect.fbIsConnected() ? 'Connected' : 'Not connected'; } } // ── Direct-mode ADB ── async function hwDirectAdbConnect() { var msg = document.getElementById('hw-direct-adb-msg'); msg.textContent = 'Requesting USB device...'; try { var device = await HWDirect.adbRequestDevice(); if (!device) { msg.textContent = 'Cancelled'; return; } msg.textContent = 'Connecting to ' + (device.name || device.serial) + '...'; await HWDirect.adbConnect(device); msg.innerHTML = 'Connected: ' + escapeHtml(device.serial || device.name) + ''; document.getElementById('hw-direct-adb-disconnect-btn').style.display = 'inline-block'; hwUpdateDirectStatus(); // Show device actions and load info hwSelectedAdb = device.serial || 'direct'; document.getElementById('hw-selected-serial').textContent = device.serial || device.name; document.getElementById('hw-device-actions').style.display = 'block'; hwDeviceInfo(); } catch (e) { msg.innerHTML = '' + escapeHtml(e.message) + ''; } } async function hwDirectAdbDisconnect() { await HWDirect.adbDisconnect(); document.getElementById('hw-direct-adb-msg').textContent = 'Disconnected'; document.getElementById('hw-direct-adb-disconnect-btn').style.display = 'none'; document.getElementById('hw-device-actions').style.display = 'none'; hwSelectedAdb = ''; hwUpdateDirectStatus(); } // ── Direct-mode Fastboot ── async function hwDirectFbConnect() { var msg = document.getElementById('hw-direct-fb-msg'); msg.textContent = 'Requesting USB device...'; try { await HWDirect.fbConnect(); msg.innerHTML = 'Fastboot device connected'; document.getElementById('hw-direct-fb-disconnect-btn').style.display = 'inline-block'; hwUpdateDirectStatus(); hwSelectedFb = 'direct'; document.getElementById('hw-fb-selected').textContent = 'Direct USB'; document.getElementById('hw-fastboot-actions').style.display = 'block'; hwFastbootInfo(); } catch (e) { msg.innerHTML = '' + escapeHtml(e.message) + ''; } } async function hwDirectFbDisconnect() { await HWDirect.fbDisconnect(); document.getElementById('hw-direct-fb-msg').textContent = 'Disconnected'; document.getElementById('hw-direct-fb-disconnect-btn').style.display = 'none'; document.getElementById('hw-fastboot-actions').style.display = 'none'; hwSelectedFb = ''; hwUpdateDirectStatus(); } // ── Direct-mode ESP32 ── async function hwDirectEspConnect() { var msg = document.getElementById('hw-direct-esp-msg'); msg.textContent = 'Select serial port...'; try { await HWDirect.espRequestPort(); msg.innerHTML = 'Serial port selected'; document.getElementById('hw-direct-esp-disconnect-btn').style.display = 'inline-block'; } catch (e) { msg.innerHTML = '' + escapeHtml(e.message) + ''; } } async function hwDirectEspDisconnect() { await HWDirect.espDisconnect(); document.getElementById('hw-direct-esp-msg').textContent = 'Disconnected'; document.getElementById('hw-direct-esp-disconnect-btn').style.display = 'none'; } // ── Archon Server Bootstrap ── function hwArchonBootstrap() { var out = document.getElementById('hw-archon-output'); if (!out) return; out.textContent = 'Discovering device and bootstrapping ArchonServer...'; // Step 1: Find connected device fetchJSON('/hardware/adb/devices').then(function(data) { var devices = data.devices || []; if (devices.length === 0) { out.textContent = 'ERROR: No ADB devices connected. Connect phone via USB.'; return; } var serial = devices[0].serial; out.textContent = 'Found device: ' + serial + '\nGetting APK path...'; // Step 2: Get the Archon APK path fetchJSON('/hardware/adb/shell', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({serial: serial, command: 'pm path com.darkhal.archon'}) }).then(function(r) { var stdout = r.stdout || ''; var apkPath = stdout.replace('package:', '').trim().split('\n')[0]; if (!apkPath || !apkPath.startsWith('/data/app/')) { out.textContent += '\nERROR: Archon app not installed on device.\npm path output: ' + stdout; return; } out.textContent += '\nAPK: ' + apkPath; // Step 3: Generate token and bootstrap var token = ''; for (var i = 0; i < 32; i++) { token += '0123456789abcdef'[Math.floor(Math.random() * 16)]; } out.textContent += '\nToken: ' + token + '\nBootstrapping...'; fetchJSON('/hardware/archon/bootstrap', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({apk_path: apkPath, token: token, port: 17321}) }).then(function(result) { if (result.ok) { out.textContent += '\nBootstrap command sent!'; out.textContent += '\nstdout: ' + (result.stdout || ''); if (result.stderr) out.textContent += '\nstderr: ' + result.stderr; out.textContent += '\n\nWaiting for server to start...'; // Step 4: Check if server started (ping via device) setTimeout(function() { fetchJSON('/hardware/adb/shell', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({serial: serial, command: 'cat /data/local/tmp/archon_server.log'}) }).then(function(logResult) { out.textContent += '\n\n── Server Log ──\n' + (logResult.stdout || logResult.stderr || 'empty'); }); }, 3000); } else { out.textContent += '\nERROR: ' + (result.error || result.stderr || JSON.stringify(result)); } }); }); }); } function hwArchonStatus() { var out = document.getElementById('hw-archon-output'); if (!out) return; out.textContent = 'Checking ArchonServer status...'; fetchJSON('/hardware/adb/devices').then(function(data) { var devices = data.devices || []; if (devices.length === 0) { out.textContent = 'No ADB devices connected.'; return; } var serial = devices[0].serial; fetchJSON('/hardware/adb/shell', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({serial: serial, command: 'cat /data/local/tmp/archon_server.log 2>&1; echo "---"; ps -A | grep archon 2>/dev/null || echo "No archon process"'}) }).then(function(r) { out.textContent = '── Server Log ──\n' + (r.stdout || 'No log file') + '\n' + (r.stderr || ''); }); }); } function hwArchonStop() { var out = document.getElementById('hw-archon-output'); if (!out) return; out.textContent = 'Stopping ArchonServer...'; fetchJSON('/hardware/adb/devices').then(function(data) { var devices = data.devices || []; if (devices.length === 0) { out.textContent = 'No ADB devices connected.'; return; } var serial = devices[0].serial; fetchJSON('/hardware/adb/shell', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({serial: serial, command: "pkill -f 'com.darkhal.archon.server.ArchonServer' 2>/dev/null && echo 'Killed' || echo 'Not running'"}) }).then(function(r) { out.textContent = r.stdout || r.stderr || 'Done'; }); }); } // ── ADB (mode-aware) ── function hwRefreshAdbDevices() { if (hwConnectionMode === 'direct') return; // Direct mode uses connect buttons var container = document.getElementById('hw-adb-devices'); if (!container) return; container.innerHTML = '
Scanning...
'; fetchJSON('/hardware/adb/devices').then(function(data) { var devices = data.devices || []; if (devices.length === 0) { container.innerHTML = '
No ADB devices connected
'; document.getElementById('hw-device-actions').style.display = 'none'; return; } var html = ''; devices.forEach(function(d) { var sel = d.serial === hwSelectedAdb ? ' style="background:rgba(99,102,241,0.08)"' : ''; html += '' + '' + '' + '' + ''; }); html += '
SerialStateModelProduct
' + escapeHtml(d.serial) + '' + escapeHtml(d.state) + '' + escapeHtml(d.model || '') + '' + escapeHtml(d.product || '') + '
'; container.innerHTML = html; }); } function hwSelectDevice(serial) { hwSelectedAdb = serial; document.getElementById('hw-selected-serial').textContent = serial; document.getElementById('hw-device-actions').style.display = 'block'; hwDeviceInfo(serial); hwRefreshAdbDevices(); } function hwDeviceInfo(serial) { var container = document.getElementById('hw-device-info'); container.innerHTML = '
Loading...
'; if (hwConnectionMode === 'direct') { HWDirect.adbGetInfo().then(function(data) { hwRenderDeviceInfo(container, data); }).catch(function(e) { container.innerHTML = '
' + escapeHtml(e.message) + '
'; }); } else { postJSON('/hardware/adb/info', {serial: serial}).then(function(data) { if (data.error) { container.innerHTML = '
' + escapeHtml(data.error) + '
'; return; } hwRenderDeviceInfo(container, data); }); } } function hwRenderDeviceInfo(container, data) { var html = ''; var keys = ['model', 'brand', 'android_version', 'sdk', 'build', 'security_patch', 'cpu_abi', 'battery', 'battery_status', 'storage_total', 'storage_used', 'storage_free', 'serialno']; keys.forEach(function(k) { if (data[k]) { var label = k.replace(/_/g, ' ').replace(/\b\w/g, function(c){return c.toUpperCase();}); html += '
' + label + '
' + escapeHtml(data[k]) + '
'; } }); container.innerHTML = html || '
No properties available
'; } function hwShell() { if (!hwSelectedAdb) { alert('No device selected'); return; } var input = document.getElementById('hw-shell-cmd'); var cmd = input.value.trim(); if (!cmd) return; var output = document.getElementById('hw-shell-output'); var existing = output.textContent; output.textContent = existing + (existing ? '\n' : '') + '$ ' + cmd + '\n...'; var promise; if (hwConnectionMode === 'direct') { promise = HWDirect.adbShell(cmd).then(function(data) { return data.output || data.error || ''; }); } else { promise = postJSON('/hardware/adb/shell', {serial: hwSelectedAdb, command: cmd}).then(function(data) { return data.output || data.error || ''; }); } promise.then(function(result) { output.textContent = existing + (existing ? '\n' : '') + '$ ' + cmd + '\n' + result; output.scrollTop = output.scrollHeight; }); input.value = ''; } function hwReboot(mode) { if (!hwSelectedAdb) { alert('No device selected'); return; } if (!confirm('Reboot device to ' + mode + '?')) return; if (hwConnectionMode === 'direct') { HWDirect.adbReboot(mode).then(function() { alert('Rebooting...'); hwDirectAdbDisconnect(); }).catch(function(e) { alert('Reboot failed: ' + e.message); }); } else { postJSON('/hardware/adb/reboot', {serial: hwSelectedAdb, mode: mode}).then(function(data) { alert(data.output || (data.success ? 'Rebooting...' : 'Failed')); setTimeout(hwRefreshAdbDevices, 3000); }); } } function hwSideload() { // Server mode only if (!hwSelectedAdb) { alert('No device selected'); return; } var filepath = document.getElementById('hw-sideload-path').value.trim(); if (!filepath) { alert('Enter file path'); return; } var progDiv = document.getElementById('hw-sideload-progress'); progDiv.style.display = 'block'; document.getElementById('hw-sideload-fill').style.width = '0%'; document.getElementById('hw-sideload-pct').textContent = '0%'; document.getElementById('hw-sideload-msg').textContent = 'Starting...'; postJSON('/hardware/adb/sideload', {serial: hwSelectedAdb, filepath: filepath}).then(function(data) { if (!data.success) { document.getElementById('hw-sideload-msg').textContent = data.error || 'Failed'; return; } hwTrackProgress(data.op_id, 'hw-sideload-fill', 'hw-sideload-pct', 'hw-sideload-msg'); }); } async function hwSideloadDirect() { // Direct mode: install APK from file picker if (!HWDirect.adbIsConnected()) { alert('No ADB device connected'); return; } var fileInput = document.getElementById('hw-sideload-file'); if (!fileInput.files.length) { alert('Select an APK file'); return; } var file = fileInput.files[0]; document.getElementById('hw-sideload-msg').textContent = 'Installing ' + file.name + '...'; document.getElementById('hw-sideload-progress').style.display = 'block'; try { var result = await HWDirect.adbInstall(file); document.getElementById('hw-sideload-msg').textContent = result.output || 'Done'; } catch (e) { document.getElementById('hw-sideload-msg').textContent = 'Failed: ' + e.message; } } function hwTrackProgress(opId, fillId, pctId, msgId) { var es = new EventSource('/hardware/progress/stream?op_id=' + encodeURIComponent(opId)); es.onmessage = function(e) { var data = JSON.parse(e.data); var fill = document.getElementById(fillId); var pct = document.getElementById(pctId); var msg = document.getElementById(msgId); if (fill) fill.style.width = data.progress + '%'; if (pct) pct.textContent = data.progress + '%'; if (msg) msg.textContent = data.message || ''; if (data.status === 'done' || data.status === 'error' || data.status === 'unknown') { es.close(); } }; es.onerror = function() { es.close(); }; } function hwPush() { // Server mode if (!hwSelectedAdb) { alert('No device selected'); return; } var local = document.getElementById('hw-push-local').value.trim(); var remote = document.getElementById('hw-push-remote').value.trim(); if (!local || !remote) { alert('Enter both local and remote paths'); return; } var msg = document.getElementById('hw-transfer-msg'); msg.textContent = 'Pushing...'; postJSON('/hardware/adb/push', {serial: hwSelectedAdb, local: local, remote: remote}).then(function(data) { msg.textContent = data.output || data.error || (data.success ? 'Done' : 'Failed'); }); } async function hwPushDirect() { // Direct mode: push file from picker if (!HWDirect.adbIsConnected()) { alert('No ADB device connected'); return; } var fileInput = document.getElementById('hw-push-file'); var remote = document.getElementById('hw-push-remote-direct').value.trim(); if (!fileInput.files.length) { alert('Select a file'); return; } if (!remote) { alert('Enter remote path'); return; } var msg = document.getElementById('hw-transfer-msg'); msg.textContent = 'Pushing...'; try { await HWDirect.adbPush(fileInput.files[0], remote); msg.textContent = 'Push complete'; } catch (e) { msg.textContent = 'Failed: ' + e.message; } } async function hwPullDirect() { // Direct mode: pull file and download if (!HWDirect.adbIsConnected()) { alert('No ADB device connected'); return; } var remote = document.getElementById('hw-push-remote-direct').value.trim(); if (!remote) { alert('Enter remote path'); return; } var msg = document.getElementById('hw-transfer-msg'); msg.textContent = 'Pulling...'; try { var blob = await HWDirect.adbPull(remote); var filename = remote.split('/').pop() || 'pulled_file'; HWDirect.downloadBlob(blob, filename); msg.textContent = 'Downloaded: ' + filename; } catch (e) { msg.textContent = 'Failed: ' + e.message; } } function hwPull() { // Server mode if (!hwSelectedAdb) { alert('No device selected'); return; } var remote = document.getElementById('hw-push-remote').value.trim(); if (!remote) { alert('Enter remote path'); return; } var msg = document.getElementById('hw-transfer-msg'); msg.textContent = 'Pulling...'; postJSON('/hardware/adb/pull', {serial: hwSelectedAdb, remote: remote}).then(function(data) { msg.textContent = data.output || data.error || (data.success ? 'Saved to: ' + data.local_path : 'Failed'); }); } function hwLogcat() { if (!hwSelectedAdb) { alert('No device selected'); return; } var lines = document.getElementById('hw-logcat-lines').value || 50; var output = document.getElementById('hw-logcat-output'); output.style.display = 'block'; output.textContent = 'Loading...'; if (hwConnectionMode === 'direct') { HWDirect.adbLogcat(parseInt(lines)).then(function(data) { output.textContent = data.output || 'No output'; output.scrollTop = output.scrollHeight; }); } else { postJSON('/hardware/adb/logcat', {serial: hwSelectedAdb, lines: parseInt(lines)}).then(function(data) { output.textContent = data.output || 'No output'; output.scrollTop = output.scrollHeight; }); } } // ── Fastboot (mode-aware) ── function hwRefreshFastbootDevices() { if (hwConnectionMode === 'direct') return; var container = document.getElementById('hw-fastboot-devices'); if (!container) return; container.innerHTML = '
Scanning...
'; fetchJSON('/hardware/fastboot/devices').then(function(data) { var devices = data.devices || []; if (devices.length === 0) { container.innerHTML = '
No Fastboot devices connected
'; document.getElementById('hw-fastboot-actions').style.display = 'none'; return; } var html = ''; devices.forEach(function(d) { html += '' + '' + ''; }); html += '
SerialState
' + escapeHtml(d.serial) + '' + escapeHtml(d.state) + '
'; container.innerHTML = html; }); } function hwSelectFastboot(serial) { hwSelectedFb = serial; document.getElementById('hw-fb-selected').textContent = serial; document.getElementById('hw-fastboot-actions').style.display = 'block'; hwFastbootInfo(serial); } function hwFastbootInfo(serial) { var container = document.getElementById('hw-fastboot-info'); container.innerHTML = '
Loading...
'; if (hwConnectionMode === 'direct') { HWDirect.fbGetInfo().then(function(data) { hwRenderFastbootInfo(container, data); }).catch(function(e) { container.innerHTML = '
' + escapeHtml(e.message) + '
'; }); } else { postJSON('/hardware/fastboot/info', {serial: serial}).then(function(data) { if (data.error) { container.innerHTML = '
' + escapeHtml(data.error) + '
'; return; } hwRenderFastbootInfo(container, data); }); } } function hwRenderFastbootInfo(container, data) { var html = ''; Object.keys(data).forEach(function(k) { if (data[k]) { var label = k.replace(/[-_]/g, ' ').replace(/\b\w/g, function(c){return c.toUpperCase();}); html += '
' + label + '
' + escapeHtml(data[k]) + '
'; } }); container.innerHTML = html || '
No info available
'; } function hwFastbootFlash() { if (!hwSelectedFb) { alert('No fastboot device selected'); return; } var partition = document.getElementById('hw-fb-partition').value; if (hwConnectionMode === 'direct') { var fileInput = document.getElementById('hw-fb-firmware-file'); if (!fileInput.files.length) { alert('Select firmware file'); return; } if (!confirm('Flash ' + partition + ' partition?')) return; var progDiv = document.getElementById('hw-fb-flash-progress'); progDiv.style.display = 'block'; document.getElementById('hw-fb-flash-fill').style.width = '0%'; document.getElementById('hw-fb-flash-pct').textContent = '0%'; document.getElementById('hw-fb-flash-msg').textContent = 'Flashing...'; HWDirect.fbFlash(partition, fileInput.files[0], function(progress) { var pct = Math.round(progress * 100); document.getElementById('hw-fb-flash-fill').style.width = pct + '%'; document.getElementById('hw-fb-flash-pct').textContent = pct + '%'; }).then(function() { document.getElementById('hw-fb-flash-msg').textContent = 'Flash complete'; }).catch(function(e) { document.getElementById('hw-fb-flash-msg').textContent = 'Failed: ' + e.message; }); } else { var filepath = document.getElementById('hw-fb-firmware').value.trim(); if (!filepath) { alert('Enter firmware file path'); return; } if (!confirm('Flash ' + partition + ' partition on ' + hwSelectedFb + '?')) return; var progDiv = document.getElementById('hw-fb-flash-progress'); progDiv.style.display = 'block'; document.getElementById('hw-fb-flash-fill').style.width = '0%'; document.getElementById('hw-fb-flash-pct').textContent = '0%'; document.getElementById('hw-fb-flash-msg').textContent = 'Starting...'; postJSON('/hardware/fastboot/flash', {serial: hwSelectedFb, partition: partition, filepath: filepath}).then(function(data) { if (!data.success) { document.getElementById('hw-fb-flash-msg').textContent = data.error || 'Failed'; return; } hwTrackProgress(data.op_id, 'hw-fb-flash-fill', 'hw-fb-flash-pct', 'hw-fb-flash-msg'); }); } } function hwFastbootReboot(mode) { if (!hwSelectedFb) { alert('No fastboot device selected'); return; } if (!confirm('Reboot fastboot device to ' + mode + '?')) return; if (hwConnectionMode === 'direct') { HWDirect.fbReboot(mode).then(function() { var msg = document.getElementById('hw-fb-msg'); msg.textContent = 'Rebooting...'; hwDirectFbDisconnect(); }).catch(function(e) { document.getElementById('hw-fb-msg').textContent = 'Failed: ' + e.message; }); } else { postJSON('/hardware/fastboot/reboot', {serial: hwSelectedFb, mode: mode}).then(function(data) { var msg = document.getElementById('hw-fb-msg'); msg.textContent = data.output || (data.success ? 'Rebooting...' : 'Failed'); setTimeout(function() { hwRefreshFastbootDevices(); hwRefreshAdbDevices(); }, 3000); }); } } function hwFastbootUnlock() { document.getElementById('hw-fb-confirm').style.display = 'block'; } function hwFastbootUnlockConfirm() { if (!hwSelectedFb) return; document.getElementById('hw-fb-confirm').style.display = 'none'; var msg = document.getElementById('hw-fb-msg'); msg.textContent = 'Sending OEM unlock...'; if (hwConnectionMode === 'direct') { HWDirect.fbOemUnlock().then(function() { msg.textContent = 'OEM Unlock sent'; }).catch(function(e) { msg.textContent = 'Failed: ' + e.message; }); } else { postJSON('/hardware/fastboot/unlock', {serial: hwSelectedFb}).then(function(data) { msg.textContent = data.output || (data.success ? 'OEM Unlock sent' : 'Failed'); }); } } // ── Serial / ESP32 (mode-aware) ── function hwRefreshSerialPorts() { if (hwConnectionMode === 'direct') return; fetchJSON('/hardware/serial/ports').then(function(data) { var ports = data.ports || []; var container = document.getElementById('hw-serial-ports'); if (container) { if (ports.length === 0) { container.innerHTML = '
No serial ports detected
'; } else { var html = ''; ports.forEach(function(p) { var vidpid = (p.vid && p.pid) ? p.vid + ':' + p.pid : ''; html += '' + '' + '' + ''; }); html += '
PortDescriptionVID:PIDManufacturer
' + escapeHtml(p.port) + '' + escapeHtml(p.desc) + '' + escapeHtml(vidpid) + '' + escapeHtml(p.manufacturer || '') + '
'; container.innerHTML = html; } } ['hw-detect-port', 'hw-flash-port', 'hw-monitor-port'].forEach(function(id) { var sel = document.getElementById(id); if (!sel) return; var val = sel.value; sel.innerHTML = ''; ports.forEach(function(p) { var opt = document.createElement('option'); opt.value = p.port; opt.textContent = p.port + ' - ' + p.desc; sel.appendChild(opt); }); if (val) sel.value = val; }); }); } function hwDetectChip() { if (hwConnectionMode === 'direct') { var msg = document.getElementById('hw-detect-result'); msg.textContent = 'Connecting to chip...'; var baud = parseInt(document.getElementById('hw-detect-baud').value || '115200'); HWDirect.espConnect(baud).then(function(result) { msg.innerHTML = 'Chip: ' + escapeHtml(result.chip) + ''; }).catch(function(e) { msg.innerHTML = '' + escapeHtml(e.message) + ''; }); return; } var port = document.getElementById('hw-detect-port').value; var baud = document.getElementById('hw-detect-baud').value; if (!port) { alert('Select a port'); return; } var result = document.getElementById('hw-detect-result'); result.textContent = 'Detecting...'; postJSON('/hardware/serial/detect', {port: port, baud: parseInt(baud)}).then(function(data) { if (data.success) { result.innerHTML = 'Chip: ' + escapeHtml(data.chip) + '' + (data.chip_id ? '
Chip ID: ' + escapeHtml(data.chip_id) : ''); } else { result.innerHTML = '' + escapeHtml(data.error || 'Detection failed') + ''; } }); } function hwFlashEsp() { if (hwConnectionMode === 'direct') { var fileInput = document.getElementById('hw-flash-firmware-file'); if (!fileInput.files.length) { alert('Select firmware file'); return; } if (!confirm('Flash firmware?')) return; var progDiv = document.getElementById('hw-esp-flash-progress'); progDiv.style.display = 'block'; document.getElementById('hw-esp-flash-fill').style.width = '0%'; document.getElementById('hw-esp-flash-pct').textContent = '0%'; document.getElementById('hw-esp-flash-msg').textContent = 'Reading file...'; var address = parseInt(document.getElementById('hw-flash-address').value || '0', 16); var baud = parseInt(document.getElementById('hw-flash-baud-direct').value || '460800'); HWDirect.readFileAsBytes(fileInput.files[0]).then(function(bytes) { // Connect if not already connected var connectPromise = HWDirect.espIsConnected() ? Promise.resolve() : HWDirect.espConnect(baud); return connectPromise.then(function() { document.getElementById('hw-esp-flash-msg').textContent = 'Flashing...'; return HWDirect.espFlash( [{ data: bytes, address: address }], function(fileIndex, written, total) { var pct = total > 0 ? Math.round((written / total) * 100) : 0; document.getElementById('hw-esp-flash-fill').style.width = pct + '%'; document.getElementById('hw-esp-flash-pct').textContent = pct + '%'; } ); }); }).then(function() { document.getElementById('hw-esp-flash-msg').textContent = 'Flash complete'; document.getElementById('hw-esp-flash-fill').style.width = '100%'; document.getElementById('hw-esp-flash-pct').textContent = '100%'; }).catch(function(e) { document.getElementById('hw-esp-flash-msg').textContent = 'Failed: ' + e.message; }); return; } // Server mode var port = document.getElementById('hw-flash-port').value; var baud = document.getElementById('hw-flash-baud').value; var firmware = document.getElementById('hw-flash-firmware').value.trim(); if (!port) { alert('Select a port'); return; } if (!firmware) { alert('Enter firmware file path'); return; } if (!confirm('Flash firmware to ' + port + '?')) return; var progDiv = document.getElementById('hw-esp-flash-progress'); progDiv.style.display = 'block'; document.getElementById('hw-esp-flash-fill').style.width = '0%'; document.getElementById('hw-esp-flash-pct').textContent = '0%'; document.getElementById('hw-esp-flash-msg').textContent = 'Starting...'; postJSON('/hardware/serial/flash', {port: port, filepath: firmware, baud: parseInt(baud)}).then(function(data) { if (!data.success) { document.getElementById('hw-esp-flash-msg').textContent = data.error || 'Failed'; return; } hwTrackProgress(data.op_id, 'hw-esp-flash-fill', 'hw-esp-flash-pct', 'hw-esp-flash-msg'); }); } function hwMonitorStart() { if (hwConnectionMode === 'direct') { var baud = parseInt(document.getElementById('hw-monitor-baud-direct').value || '115200'); var output = document.getElementById('hw-monitor-output'); HWDirect.espMonitorStart(baud, function(text) { output.textContent += text; output.scrollTop = output.scrollHeight; }).then(function() { document.getElementById('hw-monitor-start-btn').style.display = 'none'; document.getElementById('hw-monitor-stop-btn').style.display = 'inline-block'; }).catch(function(e) { alert('Monitor failed: ' + e.message); }); return; } // Server mode var port = document.getElementById('hw-monitor-port').value; var baud = document.getElementById('hw-monitor-baud').value; if (!port) { alert('Select a port'); return; } postJSON('/hardware/serial/monitor/start', {port: port, baud: parseInt(baud)}).then(function(data) { if (!data.success) { alert(data.error || 'Failed to start monitor'); return; } document.getElementById('hw-monitor-start-btn').style.display = 'none'; document.getElementById('hw-monitor-stop-btn').style.display = 'inline-block'; hwMonitorStream(); }); } function hwMonitorStop() { if (hwConnectionMode === 'direct') { HWDirect.espMonitorStop(); document.getElementById('hw-monitor-start-btn').style.display = 'inline-block'; document.getElementById('hw-monitor-stop-btn').style.display = 'none'; return; } if (hwMonitorES) { hwMonitorES.close(); hwMonitorES = null; } postJSON('/hardware/serial/monitor/stop', {}).then(function() { document.getElementById('hw-monitor-start-btn').style.display = 'inline-block'; document.getElementById('hw-monitor-stop-btn').style.display = 'none'; }); } function hwMonitorStream() { if (hwMonitorES) hwMonitorES.close(); hwMonitorES = new EventSource('/hardware/serial/monitor/stream'); var output = document.getElementById('hw-monitor-output'); hwMonitorES.onmessage = function(e) { var data = JSON.parse(e.data); if (data.type === 'data') { output.textContent += data.line + '\n'; output.scrollTop = output.scrollHeight; } else if (data.type === 'stopped') { hwMonitorES.close(); hwMonitorES = null; document.getElementById('hw-monitor-start-btn').style.display = 'inline-block'; document.getElementById('hw-monitor-stop-btn').style.display = 'none'; } }; hwMonitorES.onerror = function() { hwMonitorES.close(); hwMonitorES = null; }; } function hwMonitorSend() { var input = document.getElementById('hw-monitor-input'); var data = input.value; if (!data) return; if (hwConnectionMode === 'direct') { HWDirect.espMonitorSend(data); } else { postJSON('/hardware/serial/monitor/send', {data: data}); } input.value = ''; } function hwMonitorClear() { var output = document.getElementById('hw-monitor-output'); if (output) output.textContent = ''; } // ── Factory Flash (Direct mode, PixelFlasher PoC) ── var hwFactoryZipFile = null; function hwFactoryZipSelected(input) { if (!input.files.length) return; hwFactoryZipFile = input.files[0]; var planDiv = document.getElementById('hw-factory-plan'); var detailsDiv = document.getElementById('hw-factory-plan-details'); var checkDiv = document.getElementById('hw-factory-device-check'); detailsDiv.innerHTML = '
Reading ZIP file: ' + escapeHtml(hwFactoryZipFile.name) + ' (' + (hwFactoryZipFile.size / 1024 / 1024).toFixed(1) + ' MB)
'; planDiv.style.display = 'block'; // Check if fastboot device is connected if (HWDirect.fbIsConnected()) { HWDirect.fbGetInfo().then(function(info) { checkDiv.innerHTML = 'Fastboot device connected: ' + escapeHtml(info.product || 'Unknown') + ' (unlocked: ' + escapeHtml(info.unlocked || '?') + ')'; }); } else { checkDiv.innerHTML = 'No fastboot device connected. Connect one before flashing.'; } } async function hwFactoryFlash() { if (!hwFactoryZipFile) { alert('Select a factory image ZIP'); return; } if (!HWDirect.fbIsConnected()) { alert('Connect a fastboot device first'); return; } if (!confirm('Flash factory image? This may erase device data.')) return; var progressDiv = document.getElementById('hw-factory-progress'); var logDiv = document.getElementById('hw-factory-log'); progressDiv.style.display = 'block'; logDiv.textContent = ''; var skipUserdata = document.getElementById('hw-factory-skip-userdata').checked; function logMsg(text) { logDiv.textContent += text + '\n'; logDiv.scrollTop = logDiv.scrollHeight; } try { await HWDirect.fbFactoryFlash(hwFactoryZipFile, { wipeData: !skipUserdata }, function(status) { document.getElementById('hw-factory-msg').textContent = status.message || ''; if (status.progress !== undefined) { var pct = Math.round(status.progress * 100); document.getElementById('hw-factory-fill').style.width = pct + '%'; document.getElementById('hw-factory-pct').textContent = pct + '%'; } logMsg('[' + (status.stage || '') + '] ' + (status.message || '')); }); document.getElementById('hw-factory-msg').textContent = 'Factory flash complete'; document.getElementById('hw-factory-fill').style.width = '100%'; document.getElementById('hw-factory-pct').textContent = '100%'; } catch (e) { document.getElementById('hw-factory-msg').textContent = 'Failed: ' + e.message; logMsg('ERROR: ' + e.message); } } // ── Agent Hal Global Chat Panel ────────────────────────────────────────────── var halAgentMode = false; // false = direct chat, true = agent mode function halModeChanged(checkbox) { halAgentMode = checkbox.checked; var label = document.getElementById('hal-mode-label'); if (label) label.textContent = halAgentMode ? 'Agent' : 'Chat'; } function halToggle() { var p = document.getElementById('hal-panel'); if (!p) return; var visible = p.style.display !== 'none'; p.style.display = visible ? 'none' : 'flex'; if (!visible) { var inp = document.getElementById('hal-input'); if (inp) inp.focus(); } } function halSend() { var inp = document.getElementById('hal-input'); if (!inp) return; var msg = inp.value.trim(); if (!msg) return; inp.value = ''; inp.disabled = true; halAppend('user', msg); var container = document.getElementById('hal-messages'); fetch('/api/chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({message: msg, mode: halAgentMode ? 'agent' : 'chat'}) }).then(function(res) { var reader = res.body.getReader(); var dec = new TextDecoder(); var buf = ''; function pump() { reader.read().then(function(chunk) { if (chunk.done) { inp.disabled = false; inp.focus(); return; } buf += dec.decode(chunk.value, {stream: true}); var parts = buf.split('\n\n'); buf = parts.pop(); parts.forEach(function(part) { var line = part.replace(/^data:\s*/, '').trim(); if (!line) return; try { var d = JSON.parse(line); if (d.type === 'thought') { halAppendStyled('thought', d.content); } else if (d.type === 'action') { halAppendStyled('action', d.content); } else if (d.type === 'result') { halAppendStyled('result', d.content); } else if (d.type === 'answer') { halAppendStyled('bot', d.content); } else if (d.type === 'status') { halAppendStyled('status', d.content); } else if (d.type === 'error') { halAppendStyled('error', d.content); } else if (d.token) { // Legacy streaming token mode var last = container.lastElementChild; if (!last || !last.classList.contains('hal-msg-bot')) { last = halAppend('bot', ''); } last.textContent += d.token; halScroll(); } else if (d.done) { inp.disabled = false; inp.focus(); } } catch(e) {} }); pump(); }); } pump(); }).catch(function(e) { halAppendStyled('error', e.message); inp.disabled = false; }); } function halAppendStyled(type, text) { var msgs = document.getElementById('hal-messages'); if (!msgs) return; var div = document.createElement('div'); div.className = 'hal-msg hal-msg-' + type; if (type === 'thought') { div.style.cssText = 'font-style:italic;color:var(--text-muted,#888);font-size:0.8rem'; div.textContent = text; } else if (type === 'action') { div.style.cssText = 'font-family:monospace;color:var(--accent,#0af);font-size:0.78rem;background:rgba(0,170,255,0.08);padding:4px 8px;border-radius:4px'; div.textContent = '> ' + text; } else if (type === 'result') { div.style.cssText = 'font-family:monospace;color:var(--text-secondary,#aaa);font-size:0.75rem;max-height:100px;overflow-y:auto;white-space:pre-wrap;background:rgba(255,255,255,0.03);padding:4px 8px;border-radius:4px'; div.textContent = text; } else if (type === 'status') { div.style.cssText = 'color:var(--text-muted,#666);font-size:0.78rem;font-style:italic'; div.textContent = text; } else if (type === 'error') { div.style.cssText = 'color:var(--danger,#f55);font-size:0.82rem'; div.textContent = 'Error: ' + text; } else { div.textContent = text; } msgs.appendChild(div); halScroll(); } function halAppend(role, text) { var msgs = document.getElementById('hal-messages'); if (!msgs) return null; var div = document.createElement('div'); div.className = 'hal-msg hal-msg-' + role; div.textContent = text; msgs.appendChild(div); halScroll(); return div; } function halScroll() { var m = document.getElementById('hal-messages'); if (m) m.scrollTop = m.scrollHeight; } function halClear() { var m = document.getElementById('hal-messages'); if (m) m.innerHTML = ''; fetch('/api/chat/reset', {method: 'POST'}).catch(function() {}); } // ── Debug Console ───────────────────────────────────────────────────────────── var _dbgEs = null; var _dbgMode = 'warn'; var _dbgMessages = []; var _dbgMsgCount = 0; // Level → display config var _DBG_LEVELS = { DEBUG: { cls: 'dbg-debug', sym: '▶' }, INFO: { cls: 'dbg-info', sym: 'ℹ' }, WARNING: { cls: 'dbg-warn', sym: '⚠' }, ERROR: { cls: 'dbg-err', sym: '✕' }, CRITICAL: { cls: 'dbg-crit', sym: '☠' }, }; // Output-tagged logger names (treated as operational output in "Output Only" mode) var _OUTPUT_LOGGERS = ['msf', 'agent', 'autarch', 'output', 'scanner', 'tools']; function debugToggle(enabled) { fetch('/settings/debug/toggle', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({enabled: enabled}) }).catch(function() {}); var btn = document.getElementById('debug-toggle-btn'); if (btn) btn.style.display = enabled ? '' : 'none'; localStorage.setItem('autarch_debug', enabled ? '1' : '0'); if (enabled) { _dbgStartStream(); } else { _dbgStopStream(); } } function debugOpen() { var p = document.getElementById('debug-panel'); if (!p) return; p.style.display = 'flex'; if (!_dbgEs) _dbgStartStream(); } function debugClose() { var p = document.getElementById('debug-panel'); if (p) p.style.display = 'none'; } function _dbgStartStream() { if (_dbgEs) return; _dbgEs = new EventSource('/settings/debug/stream'); var dot = document.getElementById('debug-live-dot'); if (dot) dot.classList.add('debug-live-active'); _dbgEs.onmessage = function(e) { try { var d = JSON.parse(e.data); _dbgMessages.push(d); if (_dbgMessages.length > 5000) _dbgMessages.shift(); _dbgRenderOne(d); } catch(err) {} }; _dbgEs.onerror = function() { if (dot) dot.classList.remove('debug-live-active'); }; } function _dbgStopStream() { if (_dbgEs) { _dbgEs.close(); _dbgEs = null; } var dot = document.getElementById('debug-live-dot'); if (dot) dot.classList.remove('debug-live-active'); } function _dbgShouldShow(entry) { var lvl = (entry.level || '').toUpperCase(); switch (_dbgMode) { case 'all': return true; case 'debug': return true; // all levels, with symbols case 'verbose': return lvl !== 'DEBUG' && lvl !== 'NOTSET'; case 'warn': return lvl === 'WARNING' || lvl === 'ERROR' || lvl === 'CRITICAL'; case 'output': var name = (entry.name || '').toLowerCase(); return _OUTPUT_LOGGERS.some(function(pfx) { return name.indexOf(pfx) >= 0; }); } return true; } function _dbgFormat(entry) { var lvl = (entry.level || 'INFO').toUpperCase(); var cfg = _DBG_LEVELS[lvl] || {cls: '', sym: '·'}; var ts = new Date(entry.ts * 1000).toISOString().substr(11, 12); var sym = (_dbgMode === 'debug' || _dbgMode === 'all') ? cfg.sym + ' ' : ''; var name = _dbgMode === 'all' ? '[' + (entry.name || '') + '] ' : ''; var text = ts + ' ' + sym + '[' + lvl.substr(0,4) + '] ' + name + (entry.raw || ''); var exc = (_dbgMode === 'all' && entry.exc) ? '\n' + entry.exc : ''; return { cls: cfg.cls, text: text + exc }; } function _dbgRenderOne(entry) { if (!_dbgShouldShow(entry)) return; var out = document.getElementById('debug-output'); if (!out) return; var f = _dbgFormat(entry); var line = document.createElement('div'); line.className = 'debug-line ' + f.cls; line.textContent = f.text; out.appendChild(line); // Auto-scroll only if near bottom (within 80px) if (out.scrollHeight - out.scrollTop - out.clientHeight < 80) { out.scrollTop = out.scrollHeight; } _dbgMsgCount++; var cnt = document.getElementById('debug-msg-count'); if (cnt) cnt.textContent = _dbgMsgCount + ' msgs'; } function _dbgRerender() { var out = document.getElementById('debug-output'); if (!out) return; out.innerHTML = ''; _dbgMsgCount = 0; var cnt = document.getElementById('debug-msg-count'); if (cnt) cnt.textContent = '0 msgs'; _dbgMessages.forEach(_dbgRenderOne); } function debugSetMode(chk) { // Mutually exclusive — uncheck all others document.querySelectorAll('input[name="dbg-mode"]').forEach(function(c) { c.checked = false; }); chk.checked = true; _dbgMode = chk.value; _dbgRerender(); } function debugClear() { _dbgMessages = []; _dbgMsgCount = 0; var out = document.getElementById('debug-output'); if (out) out.innerHTML = ''; var cnt = document.getElementById('debug-msg-count'); if (cnt) cnt.textContent = '0 msgs'; fetch('/settings/debug/clear', {method: 'POST'}).catch(function() {}); } // Init debug panel on page load (function _initDebug() { document.addEventListener('DOMContentLoaded', function() { var enabled = localStorage.getItem('autarch_debug') === '1'; var btn = document.getElementById('debug-toggle-btn'); var chk = document.getElementById('debug-enable-chk'); if (btn) btn.style.display = enabled ? '' : 'none'; if (chk) chk.checked = enabled; if (enabled) { // Re-enable backend capture (survives server restarts) fetch('/settings/debug/toggle', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({enabled: true}) }).catch(function() {}); _dbgStartStream(); } // Make debug panel draggable var handle = document.getElementById('debug-drag-handle'); var panel = document.getElementById('debug-panel'); if (handle && panel) { var dragging = false, ox = 0, oy = 0; handle.addEventListener('mousedown', function(e) { dragging = true; ox = e.clientX - panel.offsetLeft; oy = e.clientY - panel.offsetTop; e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!dragging) return; panel.style.left = (e.clientX - ox) + 'px'; panel.style.top = (e.clientY - oy) + 'px'; panel.style.right = 'auto'; panel.style.bottom = 'auto'; }); document.addEventListener('mouseup', function() { dragging = false; }); } }); }());