Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.
Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00
/* 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 ;
}
2026-03-02 21:12:56 -08:00
/* ── 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 ) ;
} ) ;
}
Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.
Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00
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 = '<div style="flex:1"><strong>' + escapeHtml ( data . site ) + '</strong> '
+ '<span class="badge badge-pass" style="margin-left:6px">' + conf + '%</span>'
+ '<span class="confidence-bar"><span class="fill ' + confClass + '" style="width:' + conf + '%"></span></span>'
+ '<span style="color:var(--text-muted);margin-left:8px;font-size:0.78rem">' + escapeHtml ( data . category || '' ) + '</span>'
+ '<div class="result-detail">'
+ '<div>URL: <a href="' + escapeHtml ( data . url || '' ) + '" target="_blank">' + escapeHtml ( data . url || '' ) + '</a></div>'
+ ( data . title ? '<div>Title: ' + escapeHtml ( data . title ) + '</div>' : '' )
+ '<div>Method: ' + escapeHtml ( data . method || '' ) + ' | HTTP ' + ( data . http _code || '' ) + '</div>'
+ '</div></div>'
+ '<a href="' + escapeHtml ( data . url || '' ) + '" target="_blank" class="btn btn-small btn-success" onclick="event.stopPropagation()">Open</a>' ;
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 = '<div style="flex:1"><strong>' + escapeHtml ( data . site ) + '</strong> '
+ '<span class="badge badge-medium" style="margin-left:6px">' + conf + '%</span>'
+ '<span class="confidence-bar"><span class="fill ' + confClass + '" style="width:' + conf + '%"></span></span>'
+ '<span style="color:var(--text-muted);margin-left:8px;font-size:0.78rem">' + escapeHtml ( data . category || '' ) + '</span>'
+ '<div class="result-detail">'
+ '<div>URL: <a href="' + escapeHtml ( data . url || '' ) + '" target="_blank">' + escapeHtml ( data . url || '' ) + '</a></div>'
+ ( data . title ? '<div>Title: ' + escapeHtml ( data . title ) + '</div>' : '' )
+ '<div>Method: ' + escapeHtml ( data . method || '' ) + ' | HTTP ' + ( data . http _code || '' ) + '</div>'
+ '</div></div>'
+ '<a href="' + escapeHtml ( data . url || '' ) + '" target="_blank" class="btn btn-small" onclick="event.stopPropagation()">Open</a>' ;
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 = '<span>' + escapeHtml ( data . site ) + '</span><span style="color:var(--text-muted);font-size:0.78rem">WAF/Filtered</span>' ;
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 = '<div style="font-size:0.82rem;color:var(--text-muted)">No existing dossiers. Create one below.</div>' ;
return ;
}
var html = '' ;
dossiers . forEach ( function ( d ) {
html += '<button class="btn btn-small" style="margin:3px" onclick="saveToDossier(\'' + escapeHtml ( d . id ) + '\')">'
+ escapeHtml ( d . name ) + ' (' + d . result _count + ')</button>' ;
} ) ;
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 = '<div class="empty-state">No dossiers yet. Run a search and save results.</div>' ;
return ;
}
var html = '' ;
dossiers . forEach ( function ( d ) {
html += '<div class="dossier-card" onclick="viewDossier(\'' + escapeHtml ( d . id ) + '\')">'
+ '<h4>' + escapeHtml ( d . name ) + '</h4>'
+ '<div class="dossier-meta">' + escapeHtml ( d . target || '' ) + ' | ' + escapeHtml ( d . created ? d . created . split ( 'T' ) [ 0 ] : '' ) + '</div>'
+ '<div class="dossier-stats">' + d . result _count + ' results</div>'
+ '</div>' ;
} ) ;
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 = '<div class="empty-state">No results in this dossier.</div>' ;
return ;
}
var html = '' ;
results . forEach ( function ( r ) {
var badgeCls = r . status === 'good' ? 'badge-pass' : r . status === 'maybe' ? 'badge-medium' : 'badge-info' ;
html += '<div class="result-card ' + ( r . status || '' ) + '">'
+ '<div style="flex:1"><strong>' + escapeHtml ( r . name ) + '</strong> '
+ '<span class="badge ' + badgeCls + '">' + ( r . rate || 0 ) + '%</span>'
+ '<span style="color:var(--text-muted);margin-left:8px;font-size:0.78rem">' + escapeHtml ( r . category || '' ) + '</span></div>'
+ '<a href="' + escapeHtml ( r . url || '' ) + '" target="_blank" class="btn btn-small btn-success">Open</a>'
+ '</div>' ;
} ) ;
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 += '<tr><td>' + escapeHtml ( c . name ) + '</td><td><span class="badge ' + ( c . passed ? 'badge-pass' : 'badge-fail' ) + '">'
+ ( c . passed ? 'PASS' : 'FAIL' ) + '</span></td><td>' + escapeHtml ( c . details || '' ) + '</td></tr>' ;
} ) ;
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 = '<div class="empty-state">No threats detected.</div>' ;
} else {
threats . forEach ( function ( t ) {
var cls = t . severity === 'high' ? 'badge-high' : t . severity === 'medium' ? 'badge-medium' : 'badge-low' ;
html += '<div class="threat-item"><span class="badge ' + cls + '">' + escapeHtml ( t . severity ) . toUpperCase ( )
+ '</span><div><div class="threat-message">' + escapeHtml ( t . message )
+ '</div><div class="threat-category">' + escapeHtml ( t . category ) + '</div></div></div>' ;
} ) ;
}
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 = '<div class="empty-state">' + escapeHtml ( data . error ) + '</div>' ; return ; }
var attempts = data . attempts || [ ] ;
if ( attempts . length === 0 ) {
container . innerHTML = '<div class="empty-state">No failed login attempts found.</div>' ;
return ;
}
var html = '<table class="data-table"><thead><tr><th>IP</th><th>Attempts</th><th>Usernames</th><th>Country</th><th>ISP</th></tr></thead><tbody>' ;
attempts . forEach ( function ( a ) {
html += '<tr><td>' + escapeHtml ( a . ip ) + '</td><td>' + a . count + '</td><td>' + escapeHtml ( ( a . usernames || [ ] ) . join ( ', ' ) )
+ '</td><td>' + escapeHtml ( a . country || '-' ) + '</td><td>' + escapeHtml ( a . isp || '-' ) + '</td></tr>' ;
} ) ;
html += '</tbody></table>' ;
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 += '<div class="payload-item"><code>' + escapeHtml ( p ) + '</code><button class="btn btn-small" onclick="navigator.clipboard.writeText(this.previousElementSibling.textContent)">Copy</button></div>' ;
} ) ;
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 ? '<span class="status-dot active"></span>' : '<span class="status-dot inactive"></span>' ;
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 = '<span class="status-dot inactive"></span>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 = '<div class="empty-state">' + escapeHtml ( data . error ) + '</div>' ; return ; }
var modules = data . modules || [ ] ;
if ( modules . length === 0 ) { container . innerHTML = '<div class="empty-state">No modules found.</div>' ; 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 += '<div class="result-card" style="border-left:2px solid var(--border)"><div style="flex:1"><strong>' + escapeHtml ( m . name || m . path )
+ '</strong> <span class="badge ' + typeBadge + '">' + escapeHtml ( type ) + '</span>'
+ '<div style="font-size:0.8rem;color:var(--text-secondary);margin-top:2px">' + escapeHtml ( m . path || '' )
+ '</div></div></div>' ;
} ) ;
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 = '<div class="empty-state">' + escapeHtml ( data . error ) + '</div>' ; return ; }
var sessions = data . sessions || { } ;
var keys = Object . keys ( sessions ) ;
if ( keys . length === 0 ) { container . innerHTML = '<div class="empty-state">No active sessions.</div>' ; return ; }
var html = '<table class="data-table"><thead><tr><th>ID</th><th>Type</th><th>Target</th><th>Info</th></tr></thead><tbody>' ;
keys . forEach ( function ( id ) {
var s = sessions [ id ] ;
html += '<tr><td>' + escapeHtml ( id ) + '</td><td>' + escapeHtml ( s . type || '' ) + '</td><td>'
+ escapeHtml ( s . tunnel _peer || s . target _host || '' ) + '</td><td>' + escapeHtml ( s . info || '' ) + '</td></tr>' ;
} ) ;
html += '</tbody></table>' ;
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 = '<div class="empty-state">' + escapeHtml ( data . error ) + '</div>' ; return ; }
var modules = data . modules || [ ] ;
if ( modules . length === 0 ) { container . innerHTML = '<div class="empty-state">No modules in this category.</div>' ; return ; }
var html = '' ;
modules . forEach ( function ( m ) {
html += '<div style="padding:6px 0;border-bottom:1px solid var(--border);font-size:0.85rem">'
+ '<strong>' + escapeHtml ( m . name || '' ) + '</strong>'
+ '<div style="color:var(--text-muted);font-size:0.78rem">' + escapeHtml ( m . path || '' ) + '</div>'
+ '</div>' ;
} ) ;
if ( data . has _more ) {
html += '<div style="margin-top:8px"><button class="btn btn-small" onclick="document.getElementById(\'msf-page-' + type + '\').value='
+ ( page + 1 ) + ';browseMSFModules(\'' + type + '\')">Load More</button></div>' ;
}
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 = '<option value="">Default</option>' ;
ifaces . forEach ( function ( i ) {
var desc = i . description ? ' (' + i . description + ')' : '' ;
sel . innerHTML += '<option value="' + escapeHtml ( i . name ) + '">' + escapeHtml ( i . name ) + desc + '</option>' ;
} ) ;
} ) . catch ( function ( ) {
sel . innerHTML = '<option value="">Could not load interfaces</option>' ;
} ) ;
}
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 = '<span class="status-dot active"></span>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 = '<span class="status-dot inactive"></span>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 = '<span class="status-dot inactive"></span>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 = '<div class="empty-state">Loading...</div>' ;
postJSON ( '/wireshark/analyze/protocols' , { } ) . then ( function ( data ) {
var protocols = data . protocols || { } ;
var keys = Object . keys ( protocols ) ;
if ( keys . length === 0 ) {
container . innerHTML = '<div class="empty-state">No protocol data.</div>' ;
return ;
}
var html = '<div style="margin-bottom:8px;font-size:0.82rem;color:var(--text-secondary)">Total: ' + ( data . total || 0 ) + ' packets</div>' ;
keys . forEach ( function ( proto ) {
var d = protocols [ proto ] ;
var barWidth = Math . max ( 2 , d . percent ) ;
html += '<div style="display:flex;align-items:center;gap:10px;margin-bottom:6px;font-size:0.82rem">'
+ '<span style="width:80px;text-align:right">' + escapeHtml ( proto ) + '</span>'
+ '<div style="flex:1;height:16px;background:var(--bg-input);border-radius:3px;overflow:hidden">'
+ '<div style="height:100%;width:' + barWidth + '%;background:var(--accent);border-radius:3px"></div></div>'
+ '<span style="width:60px;color:var(--text-secondary)">' + d . count + ' (' + d . percent + '%)</span>'
+ '</div>' ;
} ) ;
container . innerHTML = html ;
} ) ;
}
function wsLoadConversations ( ) {
var container = document . getElementById ( 'ws-conversations' ) ;
container . innerHTML = '<div class="empty-state">Loading...</div>' ;
postJSON ( '/wireshark/analyze/conversations' , { } ) . then ( function ( data ) {
var convos = data . conversations || [ ] ;
if ( convos . length === 0 ) {
container . innerHTML = '<div class="empty-state">No conversations found.</div>' ;
return ;
}
var html = '<table class="data-table"><thead><tr><th>Source</th><th>Destination</th><th>Packets</th><th>Bytes</th><th>Protocols</th></tr></thead><tbody>' ;
convos . forEach ( function ( c ) {
html += '<tr><td>' + escapeHtml ( c . src ) + '</td><td>' + escapeHtml ( c . dst ) + '</td><td>' + c . packets
+ '</td><td>' + c . bytes + '</td><td>' + escapeHtml ( ( c . protocols || [ ] ) . join ( ', ' ) ) + '</td></tr>' ;
} ) ;
html += '</tbody></table>' ;
container . innerHTML = html ;
} ) ;
}
function wsLoadDNS ( ) {
var container = document . getElementById ( 'ws-dns' ) ;
container . innerHTML = '<div class="empty-state">Loading...</div>' ;
postJSON ( '/wireshark/analyze/dns' , { } ) . then ( function ( data ) {
var queries = data . queries || [ ] ;
if ( queries . length === 0 ) {
container . innerHTML = '<div class="empty-state">No DNS queries found.</div>' ;
return ;
}
var html = '<table class="data-table"><thead><tr><th>Query</th><th>Type</th><th>Count</th><th>Response</th><th>Source</th></tr></thead><tbody>' ;
queries . forEach ( function ( q ) {
html += '<tr><td>' + escapeHtml ( q . query ) + '</td><td>' + escapeHtml ( q . type ) + '</td><td>' + q . count
+ '</td><td>' + escapeHtml ( q . response || '' ) + '</td><td>' + escapeHtml ( q . src || '' ) + '</td></tr>' ;
} ) ;
html += '</tbody></table>' ;
container . innerHTML = html ;
} ) ;
}
function wsLoadHTTP ( ) {
var container = document . getElementById ( 'ws-http' ) ;
container . innerHTML = '<div class="empty-state">Loading...</div>' ;
postJSON ( '/wireshark/analyze/http' , { } ) . then ( function ( data ) {
var reqs = data . requests || [ ] ;
if ( reqs . length === 0 ) {
container . innerHTML = '<div class="empty-state">No HTTP requests found.</div>' ;
return ;
}
var html = '<table class="data-table"><thead><tr><th>Method</th><th>Host</th><th>Path</th><th>Source</th></tr></thead><tbody>' ;
reqs . forEach ( function ( r ) {
var methodCls = r . method === 'GET' ? 'badge-pass' : r . method === 'POST' ? 'badge-medium' : 'badge-info' ;
html += '<tr><td><span class="badge ' + methodCls + '">' + escapeHtml ( r . method ) + '</span></td>'
+ '<td>' + escapeHtml ( r . host ) + '</td><td>' + escapeHtml ( ( r . path || '' ) . substring ( 0 , 60 ) ) + '</td>'
+ '<td>' + escapeHtml ( r . src ) + '</td></tr>' ;
} ) ;
html += '</tbody></table>' ;
container . innerHTML = html ;
} ) ;
}
function wsLoadCredentials ( ) {
var container = document . getElementById ( 'ws-creds' ) ;
container . innerHTML = '<div class="empty-state">Loading...</div>' ;
postJSON ( '/wireshark/analyze/credentials' , { } ) . then ( function ( data ) {
var creds = data . credentials || [ ] ;
if ( creds . length === 0 ) {
container . innerHTML = '<div class="empty-state">No plaintext credentials detected.</div>' ;
return ;
}
var html = '<div style="margin-bottom:8px;font-size:0.82rem;color:var(--danger)">' + creds . length + ' credential artifact(s) found</div>' ;
html += '<table class="data-table"><thead><tr><th>Protocol</th><th>Type</th><th>Value</th><th>Source</th><th>Destination</th></tr></thead><tbody>' ;
creds . forEach ( function ( c ) {
html += '<tr><td><span class="badge badge-high">' + escapeHtml ( c . protocol ) + '</span></td>'
+ '<td>' + escapeHtml ( c . type ) + '</td><td><code>' + escapeHtml ( c . value ) + '</code></td>'
+ '<td>' + escapeHtml ( c . src ) + '</td><td>' + escapeHtml ( c . dst ) + '</td></tr>' ;
} ) ;
html += '</tbody></table>' ;
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 = '<span style="color:var(--success)">Connected: ' + escapeHtml ( device . serial || device . name ) + '</span>' ;
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 = '<span style="color:var(--danger)">' + escapeHtml ( e . message ) + '</span>' ;
}
}
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 = '<span style="color:var(--success)">Fastboot device connected</span>' ;
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 = '<span style="color:var(--danger)">' + escapeHtml ( e . message ) + '</span>' ;
}
}
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 = '<span style="color:var(--success)">Serial port selected</span>' ;
document . getElementById ( 'hw-direct-esp-disconnect-btn' ) . style . display = 'inline-block' ;
} catch ( e ) {
msg . innerHTML = '<span style="color:var(--danger)">' + escapeHtml ( e . message ) + '</span>' ;
}
}
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 = '<div class="progress-text">Scanning...</div>' ;
fetchJSON ( '/hardware/adb/devices' ) . then ( function ( data ) {
var devices = data . devices || [ ] ;
if ( devices . length === 0 ) {
container . innerHTML = '<div class="empty-state">No ADB devices connected</div>' ;
document . getElementById ( 'hw-device-actions' ) . style . display = 'none' ;
return ;
}
var html = '<table class="data-table"><thead><tr><th>Serial</th><th>State</th><th>Model</th><th>Product</th><th></th></tr></thead><tbody>' ;
devices . forEach ( function ( d ) {
var sel = d . serial === hwSelectedAdb ? ' style="background:rgba(99,102,241,0.08)"' : '' ;
html += '<tr' + sel + '><td><strong>' + escapeHtml ( d . serial ) + '</strong></td>'
+ '<td><span class="status-dot ' + ( d . state === 'device' ? 'active' : 'warning' ) + '"></span>' + escapeHtml ( d . state ) + '</td>'
+ '<td>' + escapeHtml ( d . model || '' ) + '</td>'
+ '<td>' + escapeHtml ( d . product || '' ) + '</td>'
+ '<td><button class="btn btn-primary btn-small" onclick="hwSelectDevice(\'' + escapeHtml ( d . serial ) + '\')">Select</button></td></tr>' ;
} ) ;
html += '</tbody></table>' ;
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 = '<div class="progress-text">Loading...</div>' ;
if ( hwConnectionMode === 'direct' ) {
HWDirect . adbGetInfo ( ) . then ( function ( data ) {
hwRenderDeviceInfo ( container , data ) ;
} ) . catch ( function ( e ) {
container . innerHTML = '<div class="progress-text" style="color:var(--danger)">' + escapeHtml ( e . message ) + '</div>' ;
} ) ;
} else {
postJSON ( '/hardware/adb/info' , { serial : serial } ) . then ( function ( data ) {
if ( data . error ) { container . innerHTML = '<div class="progress-text" style="color:var(--danger)">' + escapeHtml ( data . error ) + '</div>' ; 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 += '<div class="info-item"><div class="info-label">' + label + '</div><div class="info-value">' + escapeHtml ( data [ k ] ) + '</div></div>' ;
}
} ) ;
container . innerHTML = html || '<div class="progress-text">No properties available</div>' ;
}
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 = '<div class="progress-text">Scanning...</div>' ;
fetchJSON ( '/hardware/fastboot/devices' ) . then ( function ( data ) {
var devices = data . devices || [ ] ;
if ( devices . length === 0 ) {
container . innerHTML = '<div class="empty-state">No Fastboot devices connected</div>' ;
document . getElementById ( 'hw-fastboot-actions' ) . style . display = 'none' ;
return ;
}
var html = '<table class="data-table"><thead><tr><th>Serial</th><th>State</th><th></th></tr></thead><tbody>' ;
devices . forEach ( function ( d ) {
html += '<tr><td><strong>' + escapeHtml ( d . serial ) + '</strong></td>'
+ '<td>' + escapeHtml ( d . state ) + '</td>'
+ '<td><button class="btn btn-primary btn-small" onclick="hwSelectFastboot(\'' + escapeHtml ( d . serial ) + '\')">Select</button></td></tr>' ;
} ) ;
html += '</tbody></table>' ;
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 = '<div class="progress-text">Loading...</div>' ;
if ( hwConnectionMode === 'direct' ) {
HWDirect . fbGetInfo ( ) . then ( function ( data ) {
hwRenderFastbootInfo ( container , data ) ;
} ) . catch ( function ( e ) {
container . innerHTML = '<div class="progress-text" style="color:var(--danger)">' + escapeHtml ( e . message ) + '</div>' ;
} ) ;
} else {
postJSON ( '/hardware/fastboot/info' , { serial : serial } ) . then ( function ( data ) {
if ( data . error ) { container . innerHTML = '<div class="progress-text" style="color:var(--danger)">' + escapeHtml ( data . error ) + '</div>' ; 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 += '<div class="info-item"><div class="info-label">' + label + '</div><div class="info-value">' + escapeHtml ( data [ k ] ) + '</div></div>' ;
}
} ) ;
container . innerHTML = html || '<div class="progress-text">No info available</div>' ;
}
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 = '<div class="empty-state">No serial ports detected</div>' ;
} else {
var html = '<table class="data-table"><thead><tr><th>Port</th><th>Description</th><th>VID:PID</th><th>Manufacturer</th></tr></thead><tbody>' ;
ports . forEach ( function ( p ) {
var vidpid = ( p . vid && p . pid ) ? p . vid + ':' + p . pid : '' ;
html += '<tr><td><strong>' + escapeHtml ( p . port ) + '</strong></td>'
+ '<td>' + escapeHtml ( p . desc ) + '</td>'
+ '<td>' + escapeHtml ( vidpid ) + '</td>'
+ '<td>' + escapeHtml ( p . manufacturer || '' ) + '</td></tr>' ;
} ) ;
html += '</tbody></table>' ;
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 = '<option value="">Select port...</option>' ;
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 = '<span style="color:var(--success)">Chip: ' + escapeHtml ( result . chip ) + '</span>' ;
} ) . catch ( function ( e ) {
msg . innerHTML = '<span style="color:var(--danger)">' + escapeHtml ( e . message ) + '</span>' ;
} ) ;
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 = '<span style="color:var(--success)">Chip: ' + escapeHtml ( data . chip ) + '</span>'
+ ( data . chip _id ? '<br>Chip ID: ' + escapeHtml ( data . chip _id ) : '' ) ;
} else {
result . innerHTML = '<span style="color:var(--danger)">' + escapeHtml ( data . error || 'Detection failed' ) + '</span>' ;
}
} ) ;
}
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 = '<div class="progress-text">Reading ZIP file: ' + escapeHtml ( hwFactoryZipFile . name ) + ' (' + ( hwFactoryZipFile . size / 1024 / 1024 ) . toFixed ( 1 ) + ' MB)</div>' ;
planDiv . style . display = 'block' ;
// Check if fastboot device is connected
if ( HWDirect . fbIsConnected ( ) ) {
HWDirect . fbGetInfo ( ) . then ( function ( info ) {
checkDiv . innerHTML = '<span style="color:var(--success)">Fastboot device connected: ' +
escapeHtml ( info . product || 'Unknown' ) + ' (unlocked: ' + escapeHtml ( info . unlocked || '?' ) + ')</span>' ;
} ) ;
} else {
checkDiv . innerHTML = '<span style="color:var(--warning)">No fastboot device connected. Connect one before flashing.</span>' ;
}
}
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 = '' ;
Add Threat Monitor with drill-down popups, Hal agent mode, Windows defense, LLM trainer
- Threat Monitor: 7-tab monitoring page (live, connections, network intel,
threats, packet capture, DDoS mitigation, counter-attack) with real-time
SSE streaming and optimized data collection (heartbeat, cached subprocess
calls, bulk process name cache)
- Drill-down popups: Every live monitor stat is clickable, opening a popup
with detailed data (connections list with per-connection detail view,
GeoIP lookup, process kill, bandwidth, ARP spoof, port scan, DDoS status)
- Hal agent mode: Chat routes rewritten to use Agent system with
create_module tool, SSE streaming of thought/action/result steps
- Windows defense module with full security audit
- LLM trainer module and routes
- Defense landing page with platform-specific sub-pages
- Clean up stale files (get-pip.py, download.png, custom_adultsites.json)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:08:11 -08:00
inp . disabled = true ;
Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.
Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00
halAppend ( 'user' , msg ) ;
Add Threat Monitor with drill-down popups, Hal agent mode, Windows defense, LLM trainer
- Threat Monitor: 7-tab monitoring page (live, connections, network intel,
threats, packet capture, DDoS mitigation, counter-attack) with real-time
SSE streaming and optimized data collection (heartbeat, cached subprocess
calls, bulk process name cache)
- Drill-down popups: Every live monitor stat is clickable, opening a popup
with detailed data (connections list with per-connection detail view,
GeoIP lookup, process kill, bandwidth, ARP spoof, port scan, DDoS status)
- Hal agent mode: Chat routes rewritten to use Agent system with
create_module tool, SSE streaming of thought/action/result steps
- Windows defense module with full security audit
- LLM trainer module and routes
- Defense landing page with platform-specific sub-pages
- Clean up stale files (get-pip.py, download.png, custom_adultsites.json)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:08:11 -08:00
var container = document . getElementById ( 'hal-messages' ) ;
Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.
Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00
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 ) {
Add Threat Monitor with drill-down popups, Hal agent mode, Windows defense, LLM trainer
- Threat Monitor: 7-tab monitoring page (live, connections, network intel,
threats, packet capture, DDoS mitigation, counter-attack) with real-time
SSE streaming and optimized data collection (heartbeat, cached subprocess
calls, bulk process name cache)
- Drill-down popups: Every live monitor stat is clickable, opening a popup
with detailed data (connections list with per-connection detail view,
GeoIP lookup, process kill, bandwidth, ARP spoof, port scan, DDoS status)
- Hal agent mode: Chat routes rewritten to use Agent system with
create_module tool, SSE streaming of thought/action/result steps
- Windows defense module with full security audit
- LLM trainer module and routes
- Defense landing page with platform-specific sub-pages
- Clean up stale files (get-pip.py, download.png, custom_adultsites.json)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:08:11 -08:00
if ( chunk . done ) { inp . disabled = false ; inp . focus ( ) ; return ; }
Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.
Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00
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 ) ;
Add Threat Monitor with drill-down popups, Hal agent mode, Windows defense, LLM trainer
- Threat Monitor: 7-tab monitoring page (live, connections, network intel,
threats, packet capture, DDoS mitigation, counter-attack) with real-time
SSE streaming and optimized data collection (heartbeat, cached subprocess
calls, bulk process name cache)
- Drill-down popups: Every live monitor stat is clickable, opening a popup
with detailed data (connections list with per-connection detail view,
GeoIP lookup, process kill, bandwidth, ARP spoof, port scan, DDoS status)
- Hal agent mode: Chat routes rewritten to use Agent system with
create_module tool, SSE streaming of thought/action/result steps
- Windows defense module with full security audit
- LLM trainer module and routes
- Defense landing page with platform-specific sub-pages
- Clean up stale files (get-pip.py, download.png, custom_adultsites.json)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:08:11 -08:00
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 ( ) ;
}
Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.
Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00
} catch ( e ) { }
} ) ;
pump ( ) ;
} ) ;
}
pump ( ) ;
} ) . catch ( function ( e ) {
Add Threat Monitor with drill-down popups, Hal agent mode, Windows defense, LLM trainer
- Threat Monitor: 7-tab monitoring page (live, connections, network intel,
threats, packet capture, DDoS mitigation, counter-attack) with real-time
SSE streaming and optimized data collection (heartbeat, cached subprocess
calls, bulk process name cache)
- Drill-down popups: Every live monitor stat is clickable, opening a popup
with detailed data (connections list with per-connection detail view,
GeoIP lookup, process kill, bandwidth, ARP spoof, port scan, DDoS status)
- Hal agent mode: Chat routes rewritten to use Agent system with
create_module tool, SSE streaming of thought/action/result steps
- Windows defense module with full security audit
- LLM trainer module and routes
- Defense landing page with platform-specific sub-pages
- Clean up stale files (get-pip.py, download.png, custom_adultsites.json)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:08:11 -08:00
halAppendStyled ( 'error' , e . message ) ;
inp . disabled = false ;
Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.
Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00
} ) ;
}
Add Threat Monitor with drill-down popups, Hal agent mode, Windows defense, LLM trainer
- Threat Monitor: 7-tab monitoring page (live, connections, network intel,
threats, packet capture, DDoS mitigation, counter-attack) with real-time
SSE streaming and optimized data collection (heartbeat, cached subprocess
calls, bulk process name cache)
- Drill-down popups: Every live monitor stat is clickable, opening a popup
with detailed data (connections list with per-connection detail view,
GeoIP lookup, process kill, bandwidth, ARP spoof, port scan, DDoS status)
- Hal agent mode: Chat routes rewritten to use Agent system with
create_module tool, SSE streaming of thought/action/result steps
- Windows defense module with full security audit
- LLM trainer module and routes
- Defense landing page with platform-specific sub-pages
- Clean up stale files (get-pip.py, download.png, custom_adultsites.json)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:08:11 -08:00
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 ( ) ;
}
Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.
Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00
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 ; } ) ;
}
} ) ;
} ( ) ) ;