/* 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;
}
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 || '') + ' '
+ '
'
+ '
'
+ (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 || '') + ' '
+ '
'
+ '
'
+ (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 += ''
+ escapeHtml(d.name) + ' (' + d.result_count + ') ';
});
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 = 'IP Attempts Usernames Country ISP ';
attempts.forEach(function(a) {
html += '' + escapeHtml(a.ip) + ' ' + a.count + ' ' + escapeHtml((a.usernames||[]).join(', '))
+ ' ' + escapeHtml(a.country||'-') + ' ' + escapeHtml(a.isp||'-') + ' ';
});
html += '
';
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) + 'Copy
';
});
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 = 'ID Type Target Info ';
keys.forEach(function(id) {
var s = sessions[id];
html += '' + escapeHtml(id) + ' ' + escapeHtml(s.type || '') + ' '
+ escapeHtml(s.tunnel_peer || s.target_host || '') + ' ' + escapeHtml(s.info || '') + ' ';
});
html += '
';
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 += 'Load More
';
}
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 = 'Default ';
ifaces.forEach(function(i) {
var desc = i.description ? ' (' + i.description + ')' : '';
sel.innerHTML += '' + escapeHtml(i.name) + desc + ' ';
});
}).catch(function() {
sel.innerHTML = 'Could not load interfaces ';
});
}
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 = 'Source Destination Packets Bytes Protocols ';
convos.forEach(function(c) {
html += '' + escapeHtml(c.src) + ' ' + escapeHtml(c.dst) + ' ' + c.packets
+ ' ' + c.bytes + ' ' + escapeHtml((c.protocols||[]).join(', ')) + ' ';
});
html += '
';
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 = 'Query Type Count Response Source ';
queries.forEach(function(q) {
html += '' + escapeHtml(q.query) + ' ' + escapeHtml(q.type) + ' ' + q.count
+ ' ' + escapeHtml(q.response || '') + ' ' + escapeHtml(q.src || '') + ' ';
});
html += '
';
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 = 'Method Host Path Source ';
reqs.forEach(function(r) {
var methodCls = r.method === 'GET' ? 'badge-pass' : r.method === 'POST' ? 'badge-medium' : 'badge-info';
html += '' + escapeHtml(r.method) + ' '
+ '' + escapeHtml(r.host) + ' ' + escapeHtml((r.path||'').substring(0,60)) + ' '
+ '' + escapeHtml(r.src) + ' ';
});
html += '
';
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 += 'Protocol Type Value Source Destination ';
creds.forEach(function(c) {
html += '' + escapeHtml(c.protocol) + ' '
+ '' + escapeHtml(c.type) + ' ' + escapeHtml(c.value) + ' '
+ '' + escapeHtml(c.src) + ' ' + escapeHtml(c.dst) + ' ';
});
html += '
';
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 = 'Serial State Model Product ';
devices.forEach(function(d) {
var sel = d.serial === hwSelectedAdb ? ' style="background:rgba(99,102,241,0.08)"' : '';
html += '' + escapeHtml(d.serial) + ' '
+ ' ' + escapeHtml(d.state) + ' '
+ '' + escapeHtml(d.model || '') + ' '
+ '' + escapeHtml(d.product || '') + ' '
+ 'Select ';
});
html += '
';
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 = 'Serial State ';
devices.forEach(function(d) {
html += '' + escapeHtml(d.serial) + ' '
+ '' + escapeHtml(d.state) + ' '
+ 'Select ';
});
html += '
';
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 = 'Port Description VID:PID Manufacturer ';
ports.forEach(function(p) {
var vidpid = (p.vid && p.pid) ? p.vid + ':' + p.pid : '';
html += '' + escapeHtml(p.port) + ' '
+ '' + escapeHtml(p.desc) + ' '
+ '' + escapeHtml(vidpid) + ' '
+ '' + escapeHtml(p.manufacturer || '') + ' ';
});
html += '
';
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 = 'Select port... ';
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 ──────────────────────────────────────────────
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})
}).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; });
}
});
}());