Initial commit: project docs and ignore rules

This commit is contained in:
Dr Jones
2026-05-03 23:19:51 -07:00
commit 197724f7a5
55 changed files with 35824 additions and 0 deletions

View File

@@ -0,0 +1,666 @@
/**
* @file simple_web_server.c
* @brief ESP32-C5 Toolkit Web Server with Dual-Band Deauth
*
* Features:
* - Multi-target selection (2.4GHz + 5GHz)
* - Real dual-band deauth attack
* - BSSID display for proper targeting
*/
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_chip_info.h"
#include "esp_mac.h"
#include "cJSON.h"
#include "deauth_engine.h"
#include "bt_scanner.h"
#include "driver/temperature_sensor.h"
#include <string.h>
#include <stdlib.h>
// Temperature sensor handle
static temperature_sensor_handle_t temp_sensor = NULL;
#define TAG "WebServer"
// Store last scan results for target selection
static wifi_ap_record_t scan_results[30];
static uint16_t scan_count = 0;
// Forward declare HTML
static const char* get_index_html(void);
// ============================================================================
// HANDLERS
// ============================================================================
static esp_err_t root_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Serving index.html");
const char* html = get_index_html();
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, html, strlen(html));
return ESP_OK;
}
// Enhanced scan with BSSID and security
static esp_err_t scan_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "WiFi scan requested");
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time.active.min = 100,
.scan_time.active.max = 300
};
esp_wifi_scan_start(&scan_config, true);
scan_count = 30;
esp_wifi_scan_get_ap_records(&scan_count, scan_results);
cJSON *root = cJSON_CreateObject();
cJSON *networks = cJSON_CreateArray();
for (int i = 0; i < scan_count; i++) {
cJSON *ap = cJSON_CreateObject();
// SSID
cJSON_AddStringToObject(ap, "ssid", (char*)scan_results[i].ssid);
// BSSID as string
char bssid[18];
snprintf(bssid, sizeof(bssid), "%02x:%02x:%02x:%02x:%02x:%02x",
scan_results[i].bssid[0], scan_results[i].bssid[1],
scan_results[i].bssid[2], scan_results[i].bssid[3],
scan_results[i].bssid[4], scan_results[i].bssid[5]);
cJSON_AddStringToObject(ap, "bssid", bssid);
cJSON_AddNumberToObject(ap, "rssi", scan_results[i].rssi);
cJSON_AddNumberToObject(ap, "channel", scan_results[i].primary);
cJSON_AddStringToObject(ap, "band", scan_results[i].primary > 14 ? "5GHz" : "2.4GHz");
// Security
const char* security;
switch (scan_results[i].authmode) {
case WIFI_AUTH_OPEN: security = "Open"; break;
case WIFI_AUTH_WEP: security = "WEP"; break;
case WIFI_AUTH_WPA_PSK: security = "WPA"; break;
case WIFI_AUTH_WPA2_PSK: security = "WPA2"; break;
case WIFI_AUTH_WPA_WPA2_PSK: security = "WPA/WPA2"; break;
case WIFI_AUTH_WPA3_PSK: security = "WPA3"; break;
case WIFI_AUTH_WPA2_WPA3_PSK: security = "WPA2/WPA3"; break;
default: security = "Unknown";
}
cJSON_AddStringToObject(ap, "security", security);
cJSON_AddNumberToObject(ap, "index", i);
cJSON_AddItemToArray(networks, ap);
}
cJSON_AddItemToObject(root, "networks", networks);
cJSON_AddNumberToObject(root, "count", scan_count);
char *json_str = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
cJSON_Delete(root);
ESP_LOGI(TAG, "Found %d networks", scan_count);
return ESP_OK;
}
// System info with attack status and ESP32-C5 features
static esp_err_t sysinfo_handler(httpd_req_t *req) {
cJSON *root = cJSON_CreateObject();
// Chip info
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
cJSON_AddStringToObject(root, "chip", "ESP32-C5");
cJSON_AddNumberToObject(root, "cores", chip_info.cores);
cJSON_AddNumberToObject(root, "revision", chip_info.revision);
// Memory
cJSON_AddNumberToObject(root, "heap", esp_get_free_heap_size());
cJSON_AddNumberToObject(root, "min_heap", esp_get_minimum_free_heap_size());
// Temperature sensor
float temp_celsius = 0;
if (temp_sensor == NULL) {
temperature_sensor_config_t temp_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
temperature_sensor_install(&temp_config, &temp_sensor);
}
if (temp_sensor) {
temperature_sensor_enable(temp_sensor);
temperature_sensor_get_celsius(temp_sensor, &temp_celsius);
temperature_sensor_disable(temp_sensor);
}
cJSON_AddNumberToObject(root, "temp", (int)(temp_celsius * 10) / 10.0);
// MAC address
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
char mac_str[18];
snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
cJSON_AddStringToObject(root, "mac", mac_str);
// Features
cJSON_AddStringToObject(root, "features", "WiFi6,BLE5,Thread");
cJSON_AddStringToObject(root, "version", "2.0.0");
cJSON_AddBoolToObject(root, "attacking", deauth_is_running());
// Get attack stats if running
if (deauth_is_running()) {
uint32_t total, p24, p5, elapsed;
deauth_get_stats(&total, &p24, &p5, &elapsed);
cJSON_AddNumberToObject(root, "packets_total", total);
cJSON_AddNumberToObject(root, "packets_24ghz", p24);
cJSON_AddNumberToObject(root, "packets_5ghz", p5);
cJSON_AddNumberToObject(root, "elapsed", elapsed);
}
char *json_str = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
cJSON_Delete(root);
return ESP_OK;
}
// REAL deauth start - parses JSON with targets
static esp_err_t deauth_start_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Deauth start requested");
// Read POST body
char buf[512];
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (ret <= 0) {
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No data\"}");
return ESP_OK;
}
buf[ret] = '\0';
ESP_LOGI(TAG, "Received: %s", buf);
cJSON *root = cJSON_Parse(buf);
if (!root) {
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid JSON\"}");
return ESP_OK;
}
deauth_target_t target_24 = {0};
deauth_target_t target_5 = {0};
// Parse 2.4GHz target
cJSON *t24 = cJSON_GetObjectItem(root, "target_24ghz");
if (t24) {
cJSON *bssid = cJSON_GetObjectItem(t24, "bssid");
cJSON *ssid = cJSON_GetObjectItem(t24, "ssid");
cJSON *channel = cJSON_GetObjectItem(t24, "channel");
if (cJSON_IsString(bssid) && cJSON_IsNumber(channel)) {
sscanf(bssid->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&target_24.bssid[0], &target_24.bssid[1], &target_24.bssid[2],
&target_24.bssid[3], &target_24.bssid[4], &target_24.bssid[5]);
target_24.channel = (uint8_t)channel->valueint;
if (cJSON_IsString(ssid)) {
strncpy(target_24.ssid, ssid->valuestring, sizeof(target_24.ssid) - 1);
}
target_24.active = true;
ESP_LOGI(TAG, "2.4GHz target: %s CH:%d", target_24.ssid, target_24.channel);
}
}
// Parse 5GHz target
cJSON *t5 = cJSON_GetObjectItem(root, "target_5ghz");
if (t5) {
cJSON *bssid = cJSON_GetObjectItem(t5, "bssid");
cJSON *ssid = cJSON_GetObjectItem(t5, "ssid");
cJSON *channel = cJSON_GetObjectItem(t5, "channel");
if (cJSON_IsString(bssid) && cJSON_IsNumber(channel)) {
sscanf(bssid->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&target_5.bssid[0], &target_5.bssid[1], &target_5.bssid[2],
&target_5.bssid[3], &target_5.bssid[4], &target_5.bssid[5]);
target_5.channel = (uint8_t)channel->valueint;
if (cJSON_IsString(ssid)) {
strncpy(target_5.ssid, ssid->valuestring, sizeof(target_5.ssid) - 1);
}
target_5.active = true;
ESP_LOGI(TAG, "5GHz target: %s CH:%d", target_5.ssid, target_5.channel);
}
}
// Get duration (default 30 seconds)
cJSON *dur = cJSON_GetObjectItem(root, "duration");
uint32_t duration = cJSON_IsNumber(dur) ? dur->valueint : 30;
cJSON_Delete(root);
// Start the REAL attack
bool success = deauth_start_attack(
target_24.active ? &target_24 : NULL,
target_5.active ? &target_5 : NULL,
duration
);
httpd_resp_set_type(req, "application/json");
if (success) {
ESP_LOGI(TAG, "Attack started for %lu seconds", duration);
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"Attack started\"}");
} else {
ESP_LOGW(TAG, "Failed to start attack");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to start - check targets\"}");
}
return ESP_OK;
}
// Stop attack
static esp_err_t deauth_stop_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Deauth stop requested");
deauth_stop_attack();
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"Attack stopped\"}");
return ESP_OK;
}
// Attack status endpoint
static esp_err_t deauth_status_handler(httpd_req_t *req) {
cJSON *root = cJSON_CreateObject();
bool running = deauth_is_running();
cJSON_AddBoolToObject(root, "running", running);
if (running) {
uint32_t total, p24, p5, elapsed;
deauth_get_stats(&total, &p24, &p5, &elapsed);
cJSON_AddNumberToObject(root, "packets_total", total);
cJSON_AddNumberToObject(root, "packets_24ghz", p24);
cJSON_AddNumberToObject(root, "packets_5ghz", p5);
cJSON_AddNumberToObject(root, "elapsed", elapsed);
}
char *json_str = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
cJSON_Delete(root);
return ESP_OK;
}
// Bluetooth scan start (NimBLE)
static esp_err_t bt_scan_start_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "BLE scan start requested");
httpd_resp_set_type(req, "application/json");
bool success = bt_scan_start();
if (success) {
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"BLE scan started\"}");
} else {
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to start BLE scan\"}");
}
return ESP_OK;
}
// Bluetooth scan stop
static esp_err_t bt_scan_stop_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "BLE scan stop requested");
httpd_resp_set_type(req, "application/json");
bool success = bt_scan_stop();
if (success) {
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"BLE scan stopped\"}");
} else {
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to stop BLE scan\"}");
}
return ESP_OK;
}
// Bluetooth devices list
static esp_err_t bt_devices_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "BLE devices requested");
bt_device_t bt_devs[30];
int count = bt_get_devices(bt_devs, 30);
cJSON *root = cJSON_CreateObject();
cJSON *device_array = cJSON_CreateArray();
for (int i = 0; i < count; i++) {
cJSON *dev = cJSON_CreateObject();
// MAC address
char addr[18];
snprintf(addr, sizeof(addr), "%02X:%02X:%02X:%02X:%02X:%02X",
bt_devs[i].addr[0], bt_devs[i].addr[1],
bt_devs[i].addr[2], bt_devs[i].addr[3],
bt_devs[i].addr[4], bt_devs[i].addr[5]);
cJSON_AddStringToObject(dev, "addr", addr);
cJSON_AddStringToObject(dev, "name", bt_devs[i].name[0] ? bt_devs[i].name : "Unknown");
cJSON_AddNumberToObject(dev, "rssi", bt_devs[i].rssi);
cJSON_AddStringToObject(dev, "type", "BLE");
cJSON_AddItemToArray(device_array, dev);
}
cJSON_AddItemToObject(root, "devices", device_array);
cJSON_AddNumberToObject(root, "count", count);
char *json_str = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
cJSON_Delete(root);
ESP_LOGI(TAG, "Returned %d BLE devices", count);
return ESP_OK;
}
// ============================================================================
// URI DEFINITIONS
// ============================================================================
static httpd_uri_t uri_root = {.uri = "/", .method = HTTP_GET, .handler = root_handler};
static httpd_uri_t uri_scan = {.uri = "/api/scan", .method = HTTP_GET, .handler = scan_handler};
static httpd_uri_t uri_sysinfo = {.uri = "/api/system-info", .method = HTTP_GET, .handler = sysinfo_handler};
static httpd_uri_t uri_deauth_start = {.uri = "/api/deauth/start", .method = HTTP_POST, .handler = deauth_start_handler};
static httpd_uri_t uri_deauth_stop = {.uri = "/api/deauth/stop", .method = HTTP_GET, .handler = deauth_stop_handler};
static httpd_uri_t uri_deauth_status = {.uri = "/api/deauth/status", .method = HTTP_GET, .handler = deauth_status_handler};
static httpd_uri_t uri_bt_scan_start = {.uri = "/api/bt/scan/start", .method = HTTP_GET, .handler = bt_scan_start_handler};
static httpd_uri_t uri_bt_scan_stop = {.uri = "/api/bt/scan/stop", .method = HTTP_GET, .handler = bt_scan_stop_handler};
static httpd_uri_t uri_bt_devices = {.uri = "/api/bt/devices", .method = HTTP_GET, .handler = bt_devices_handler};
// ============================================================================
// WEB SERVER
// ============================================================================
static httpd_handle_t server = NULL;
httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 8192;
config.max_uri_handlers = 15;
ESP_LOGI(TAG, "Starting web server on port %d", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
httpd_register_uri_handler(server, &uri_root);
httpd_register_uri_handler(server, &uri_scan);
httpd_register_uri_handler(server, &uri_sysinfo);
httpd_register_uri_handler(server, &uri_deauth_start);
httpd_register_uri_handler(server, &uri_deauth_stop);
httpd_register_uri_handler(server, &uri_deauth_status);
httpd_register_uri_handler(server, &uri_bt_scan_start);
httpd_register_uri_handler(server, &uri_bt_scan_stop);
httpd_register_uri_handler(server, &uri_bt_devices);
ESP_LOGI(TAG, "Web Server started at http://192.168.4.1");
} else {
ESP_LOGE(TAG, "Failed to start web server!");
}
return server;
}
void stop_webserver(void) {
if (server) {
httpd_stop(server);
server = NULL;
}
}
void init_web_server(void) {
start_webserver();
}
// ============================================================================
// HTML - Multi-target selection with Channel Graph
// ============================================================================
static const char* get_index_html(void) {
static const char html[] =
"<!DOCTYPE html>"
"<html><head>"
"<meta charset='UTF-8'>"
"<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
"<title>ESP32-C5 Toolkit</title>"
"<style>"
"*{margin:0;padding:0;box-sizing:border-box}"
"body{font-family:'Courier New',monospace;background:#0a0a0a;color:#0f0;padding:15px}"
"h1{text-align:center;margin-bottom:15px;text-shadow:0 0 10px #0f0}"
"h3{margin:10px 0 5px;color:#0f0}"
".container{max-width:900px;margin:0 auto}"
".panel{background:#111;border:1px solid #0f0;padding:15px;margin:10px 0;border-radius:5px}"
".btn{background:#0f0;color:#000;border:none;padding:8px 15px;cursor:pointer;font-family:inherit;margin:3px;border-radius:3px;font-weight:bold}"
".btn:hover{background:#0c0}"
".btn-danger{background:#f00;color:#fff}"
".btn-danger:hover{background:#c00}"
".btn-warn{background:#f80;color:#000}"
"#networks{max-height:250px;overflow-y:auto}"
".network{padding:8px;border:1px solid #333;margin:3px 0;cursor:pointer;font-size:0.9em}"
".network:hover{border-color:#0f0}"
".network.selected-24{background:#003;border-color:#08f}"
".network.selected-5{background:#330;border-color:#f80}"
".ssid{font-weight:bold}"
".bssid{color:#888;font-size:0.85em}"
".info{color:#888;font-size:0.85em}"
".band-24{color:#08f}.band-5{color:#f80}"
".targets{display:flex;gap:10px;flex-wrap:wrap;margin:10px 0}"
".target-card{flex:1;min-width:200px;padding:10px;border-radius:5px}"
".target-24{background:#002;border:2px solid #08f}"
".target-5{background:#220;border:2px solid #f80}"
".status{padding:10px;background:#001;border:1px solid #0f0;margin-top:10px}"
".attack-running{color:#f00;animation:blink 1s infinite}"
"@keyframes blink{50%{opacity:0.5}}"
".warn{background:#220;border:1px solid #f80;padding:10px;margin:10px 0;color:#fa0}"
"input[type=number]{background:#222;border:1px solid #0f0;color:#0f0;padding:5px;width:60px}"
".channel-graph{display:flex;align-items:flex-end;height:60px;gap:2px;padding:5px 0;border-bottom:1px solid #333}"
".channel-bar{flex:1;background:#0f0;min-width:12px;transition:height 0.3s;min-height:3px;border-radius:2px 2px 0 0}"
".channel-bar:hover{background:#0ff}"
".channel-label{text-align:center;font-size:0.7em;color:#888}"
".bar-container{display:flex;flex-direction:column;align-items:center;flex:1}"
".graph-section{margin:10px 0}"
".graphs-container{display:flex;gap:15px;flex-wrap:wrap}"
".graph-box{flex:1;min-width:280px}"
"</style>"
"</head><body>"
"<div class='container'>"
"<h1>&#x1F4E1; ESP32-C5 TOOLKIT</h1>"
"<div class='panel'>"
"<h2>&#x1F50D; WiFi Scanner</h2>"
"<button class='btn' onclick='scanNetworks()'>SCAN ALL BANDS</button>"
"<span id='scan-status' style='margin-left:10px'></span>"
"<div id='networks' style='margin-top:10px'></div>"
"</div>"
"<div class='panel'>"
"<h2>&#x1F4F6; Channel Usage</h2>"
"<div class='graphs-container'>"
"<div class='graph-box'>"
"<h3 class='band-24'>2.4 GHz (CH 1-13)</h3>"
"<div class='channel-graph' id='graph-24'></div>"
"</div>"
"<div class='graph-box'>"
"<h3 class='band-5'>5 GHz</h3>"
"<div class='channel-graph' id='graph-5'></div>"
"</div>"
"</div>"
"</div>"
"<div class='panel'>"
"<h2>&#x26A1; Dual-Band Deauth</h2>"
"<div class='warn'>&#x26A0; WiFi AP offline during attack! Auto-restores after.</div>"
"<p>Click networks to select targets:</p>"
"<div class='targets'>"
"<div class='target-card target-24'>2.4GHz: <span id='t24-name'>None</span></div>"
"<div class='target-card target-5'>5GHz: <span id='t5-name'>None</span></div>"
"</div>"
"<p>Duration: <input type='number' id='duration' value='30' min='10' max='300'> sec</p>"
"<button class='btn btn-danger' onclick='startDeauth()'>&#x26A1; START</button>"
"<button class='btn' onclick='stopDeauth()'>STOP</button>"
"<button class='btn btn-warn' onclick='clearTargets()'>CLEAR</button>"
"<div class='status' id='deauth-status'>Status: Idle</div>"
"</div>"
"<div class='panel'>"
"<h2>&#x1F4F6; Bluetooth Scanner</h2>"
"<button class='btn' onclick='startBtScan()'>START BT SCAN</button>"
"<button class='btn' onclick='stopBtScan()'>STOP SCAN</button>"
"<span id='bt-scan-status' style='margin-left:10px'></span>"
"<div id='bt-devices' style='margin-top:10px;max-height:200px;overflow-y:auto'></div>"
"</div>"
"<div class='panel'>"
"<h2>&#x1F4BB; System</h2>"
"<div id='sysinfo'>Loading...</div>"
"</div>"
"</div>"
"<script>"
"let networks=[];"
"let target24=null,target5=null;"
"const ch5list=[36,40,44,48,149,153,157,161,165];"
"function initGraphs(){"
"let h24='';for(let i=1;i<=13;i++)h24+='<div class=\"bar-container\"><div class=\"channel-bar\" id=\"b24-'+i+'\" style=\"height:2px;background:#0f0\"></div><div class=\"channel-label\">'+i+'</div></div>';"
"document.getElementById('graph-24').innerHTML=h24;"
"let h5='';ch5list.forEach(c=>h5+='<div class=\"bar-container\"><div class=\"channel-bar\" id=\"b5-'+c+'\" style=\"height:2px;background:#0f0\"></div><div class=\"channel-label\">'+c+'</div></div>');"
"document.getElementById('graph-5').innerHTML=h5;}"
"function updateChannelGraph(networks){"
"let ch24=Array(14).fill(0);"
"let ch5={36:0,40:0,44:0,48:0,149:0,153:0,157:0,161:0,165:0};"
"networks.forEach(n=>{"
"if(n.channel<=13)ch24[n.channel]++;"
"else if(ch5[n.channel]!==undefined)ch5[n.channel]++;"
"});"
"let max24=Math.max(...ch24,1);"
"for(let i=1;i<=13;i++){"
"let count=ch24[i];"
"let h=count>0?Math.max(8,Math.round((count/max24)*55)):3;"
"let bar=document.getElementById('b24-'+i);"
"if(bar){"
"bar.style.height=h+'px';"
"bar.title='CH'+i+': '+count+' networks';"
"bar.style.background=count===0?'#0a0':count<=2?'#0f0':count<=4?'#ff0':'#f00';"
"}"
"}"
"let max5=Math.max(...Object.values(ch5),1);"
"ch5list.forEach(c=>{"
"let count=ch5[c]||0;"
"let h=count>0?Math.max(8,Math.round((count/max5)*55)):3;"
"let bar=document.getElementById('b5-'+c);"
"if(bar){"
"bar.style.height=h+'px';"
"bar.title='CH'+c+': '+count+' networks';"
"bar.style.background=count===0?'#0a0':count<=2?'#0f0':count<=4?'#ff0':'#f00';"
"}"
"});"
"}"
"async function scanNetworks(){"
"document.getElementById('scan-status').textContent='Scanning...';"
"document.getElementById('networks').innerHTML='<p>Scanning...</p>';"
"try{"
"const res=await fetch('/api/scan');"
"const data=await res.json();"
"networks=data.networks;"
"document.getElementById('scan-status').textContent='Found '+data.count;"
"renderNetworks();"
"updateChannelGraph(networks);"
"}catch(e){document.getElementById('scan-status').textContent='Error';}}"
"function renderNetworks(){"
"let html='';"
"networks.forEach((n,i)=>{"
"let cls='network';"
"if(target24&&target24.bssid===n.bssid)cls+=' selected-24';"
"if(target5&&target5.bssid===n.bssid)cls+=' selected-5';"
"html+='<div class=\"'+cls+'\" onclick=\"selectNetwork('+i+')\">';"
"html+='<span class=\"ssid\">'+n.ssid+'</span> ';"
"html+='<span class=\"info\">CH:'+n.channel+' '+n.rssi+'dBm <span class=\"'+(n.channel<=14?'band-24':'band-5')+'\">'+n.band+'</span> '+n.security+'</span>';"
"html+='</div>';});"
"document.getElementById('networks').innerHTML=html||'No networks';}"
"function selectNetwork(i){const n=networks[i];if(n.channel<=14){target24=n;document.getElementById('t24-name').textContent=n.ssid;}else{target5=n;document.getElementById('t5-name').textContent=n.ssid;}renderNetworks();}"
"function clearTargets(){target24=null;target5=null;document.getElementById('t24-name').textContent='None';document.getElementById('t5-name').textContent='None';renderNetworks();}"
"async function startDeauth(){"
"if(!target24&&!target5){alert('Select target first!');return;}"
"const dur=document.getElementById('duration').value;"
"if(!confirm('Start attack for '+dur+'s?\\nWiFi will be offline!'))return;"
"const body={duration:parseInt(dur)};"
"if(target24)body.target_24ghz={ssid:target24.ssid,bssid:target24.bssid,channel:target24.channel};"
"if(target5)body.target_5ghz={ssid:target5.ssid,bssid:target5.bssid,channel:target5.channel};"
"try{await fetch('/api/deauth/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});"
"document.getElementById('deauth-status').innerHTML='<span class=\"attack-running\">ATTACKING!</span>';}"
"catch(e){document.getElementById('deauth-status').textContent='Error';}}"
"async function stopDeauth(){await fetch('/api/deauth/stop');document.getElementById('deauth-status').textContent='Stopped';}"
"async function loadSysInfo(){try{const r=await fetch('/api/system-info');const d=await r.json();"
"let h='<b>'+d.chip+'</b> Rev:'+d.revision+' | Temp:<b>'+d.temp+'°C</b> | Heap:'+(d.heap/1024).toFixed(0)+'KB<br>';"
"h+='MAC:'+d.mac+' | '+d.features+' | v'+d.version;"
"if(d.attacking)h+='<br><span class=\"attack-running\">⚡ ATTACKING</span> Pkts:'+d.packets_total;"
"document.getElementById('sysinfo').innerHTML=h;}catch(e){}}"
"let btScanInterval=null;"
"async function startBtScan(){"
"document.getElementById('bt-scan-status').textContent='Starting scan...';"
"document.getElementById('bt-devices').innerHTML='<p>Scanning...</p>';"
"try{"
"const res=await fetch('/api/bt/scan/start');"
"const data=await res.json();"
"if(data.status==='ok'){"
"document.getElementById('bt-scan-status').textContent='Scanning...';"
"btScanInterval=setInterval(updateBtDevices,2000);"
"updateBtDevices();"
"}else{document.getElementById('bt-scan-status').textContent='Error: '+data.message;}"
"}catch(e){document.getElementById('bt-scan-status').textContent='Error';}}"
"async function stopBtScan(){"
"if(btScanInterval){clearInterval(btScanInterval);btScanInterval=null;}"
"try{await fetch('/api/bt/scan/stop');"
"document.getElementById('bt-scan-status').textContent='Stopped';"
"}catch(e){}}"
"async function updateBtDevices(){"
"try{"
"const res=await fetch('/api/bt/devices');"
"const data=await res.json();"
"let html='';"
"if(data.devices&&data.devices.length>0){"
"data.devices.forEach(dev=>{"
"html+='<div class=\"network\">';"
"html+='<span class=\"ssid\">'+(dev.name||'Unknown')+'</span> ';"
"html+='<span class=\"info\">'+dev.addr+' '+dev.rssi+'dBm <span class=\"band-24\">'+dev.type+'</span></span>';"
"html+='</div>';"
"});"
"}else{html='<p>No devices found</p>';}"
"document.getElementById('bt-devices').innerHTML=html;"
"document.getElementById('bt-scan-status').textContent='Found '+data.count+' devices';"
"}catch(e){document.getElementById('bt-scan-status').textContent='Error loading devices';}}"
"initGraphs();loadSysInfo();setInterval(loadSysInfo,5000);"
"</script>"
"</body></html>";
return html;
}