mirror of
https://github.com/htotoo/DarkMesh.git
synced 2026-07-01 20:01:40 +00:00
751 lines
31 KiB
HTML
751 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>DarkMesh</title>
|
|
<style>
|
|
:root {
|
|
--bg-color: #121212;
|
|
--panel-color: #1e1e1e;
|
|
--panel-color-alt: #242424;
|
|
--text-color: #e0e0e0;
|
|
--primary-color: #00ff7f;
|
|
--border-color: #333;
|
|
--danger-color: #ff4136;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Courier New', Courier, monospace;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-color);
|
|
padding: 1rem;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
header {
|
|
grid-column: 1 / -1;
|
|
text-align: center;
|
|
margin-bottom: 1rem;
|
|
border-bottom: 1px solid var(--border-color);
|
|
padding-bottom: 1rem;
|
|
}
|
|
|
|
header h1 {
|
|
color: var(--primary-color);
|
|
text-shadow: 0 0 5px var(--primary-color);
|
|
}
|
|
|
|
.panel {
|
|
background-color: var(--panel-color);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.panel.full-width {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
h2 {
|
|
color: var(--primary-color);
|
|
border-bottom: 1px solid var(--border-color);
|
|
padding-bottom: 0.5rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
button {
|
|
font-family: inherit;
|
|
background-color: var(--primary-color);
|
|
color: var(--bg-color);
|
|
border: none;
|
|
padding: 0.75rem 1rem;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
border-radius: 5px;
|
|
transition: all 0.2s ease;
|
|
margin-top: auto;
|
|
}
|
|
|
|
button:hover {
|
|
box-shadow: 0 0 10px var(--primary-color);
|
|
}
|
|
|
|
button#stop-attack-btn {
|
|
background-color: var(--danger-color);
|
|
}
|
|
|
|
button#stop-attack-btn:hover {
|
|
box-shadow: 0 0 10px var(--danger-color);
|
|
}
|
|
|
|
input,
|
|
textarea,
|
|
select {
|
|
font-family: inherit;
|
|
background-color: var(--bg-color);
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-color);
|
|
padding: 0.5rem;
|
|
border-radius: 5px;
|
|
width: 100%;
|
|
}
|
|
|
|
textarea {
|
|
resize: vertical;
|
|
min-height: 60px;
|
|
}
|
|
|
|
label {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.input-group {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.input-group input {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.btn-all {
|
|
padding: 0 1rem;
|
|
margin-top: 0;
|
|
background-color: var(--border-color);
|
|
color: var(--text-color);
|
|
font-weight: normal;
|
|
}
|
|
|
|
.param-group {
|
|
padding: 1rem;
|
|
border: 1px dashed var(--border-color);
|
|
border-radius: 5px;
|
|
margin-top: 0.5rem;
|
|
display: none;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.param-group.active {
|
|
display: flex;
|
|
}
|
|
|
|
.param-group p {
|
|
margin-bottom: 0.5rem;
|
|
font-size: 0.9rem;
|
|
color: #aaa;
|
|
}
|
|
|
|
.coord-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.config-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
#status-value {
|
|
font-weight: bold;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.note {
|
|
font-size: 0.8rem;
|
|
color: #aaa;
|
|
font-style: italic;
|
|
}
|
|
|
|
.note code {
|
|
background-color: #000;
|
|
padding: 2px 5px;
|
|
border-radius: 3px;
|
|
font-style: normal;
|
|
}
|
|
|
|
.table-wrapper {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
th,
|
|
td {
|
|
padding: 0.5rem;
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
#node-list-body tr:nth-child(even) {
|
|
background-color: var(--panel-color-alt);
|
|
}
|
|
|
|
#node-list-body tr td:first-child {
|
|
cursor: pointer;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
th {
|
|
position: sticky;
|
|
top: 0;
|
|
background-color: var(--panel-color);
|
|
}
|
|
|
|
#debug-log {
|
|
background-color: #000;
|
|
height: 300px;
|
|
overflow-y: auto;
|
|
padding: 1rem;
|
|
border-radius: 5px;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="container">
|
|
<header>
|
|
<h1>💀 DarkMesh 💀</h1>
|
|
<p>Meshtastic Pen-Testing Suite</p>
|
|
</header>
|
|
|
|
<div class="panel">
|
|
<h2>Radio Configuration</h2>
|
|
<label for="radio-freq">Frequency (MHz)</label>
|
|
<input type="number" id="radio-freq" value="869.525" step="0.01">
|
|
<label for="lora-preset-selector">LoRa Presets</label>
|
|
<select id="lora-preset-selector">
|
|
<option value="long_fast">LongFast (Default)</option>
|
|
<option value="long_moderate">LongModerate</option>
|
|
<option value="long_slow">LongSlow</option>
|
|
<option value="medium_slow">MediumSlow</option>
|
|
<option value="medium_fast">MediumFast</option>
|
|
<option value="short_slow">ShortSlow</option>
|
|
<option value="short_fast">ShortFast</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
<div class="config-grid">
|
|
<div><label for="lora-bw">Bandwidth</label><input type="number" id="lora-bw" value="250.0" step="0.1">
|
|
</div>
|
|
<div><label for="lora-sf">Spread Factor</label><input type="number" id="lora-sf" value="11" min="7"
|
|
max="12"></div>
|
|
<div><label for="lora-cr">Coding Rate</label><input type="number" id="lora-cr" value="5" min="5"
|
|
max="8"></div>
|
|
</div>
|
|
<label for="radio-power">Output Power (dBm)</label>
|
|
<input type="number" id="radio-power" value="22" min="2" max="22">
|
|
<label for="radio-chanhash">Channel Hash</label>
|
|
<input type="number" id="radio-chanhash" value="8" min="0" max="255">
|
|
<button id="apply-config-btn">Apply Configuration</button>
|
|
</div>
|
|
|
|
|
|
<div class="panel">
|
|
<h2>WiFi STA Configuration</h2>
|
|
|
|
<label for="wifi-ssid">SSID</label>
|
|
<input type="text" id="wifi-ssid" placeholder="Enter WiFi SSID">
|
|
|
|
<label for="wifi-password">Password</label>
|
|
<input type="password" id="wifi-password" placeholder="Enter WiFi password">
|
|
|
|
<button id="save-wifi-btn">Save WiFi Configuration</button>
|
|
|
|
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>Continuous Attacks</h2>
|
|
<p>Status: <span id="status-value">Idle</span></p>
|
|
|
|
<label for="attack-interval">Attack Interval (s)</label>
|
|
<input type="number" id="attack-interval" min="10" max="7200" value="10" title="The delay between attacks.">
|
|
<label for="attack-mqtt">Ok to MQTT</label>
|
|
<input type="checkbox" id="attack-mqtt" title="Allow attacks to be sent via MQTT.">
|
|
|
|
<select id="attack-selector">
|
|
<option value="none">-- Select Attack --</option>
|
|
<option value="node_flood">Flood with Nodes</option>
|
|
<option value="name_change">Namechange - Emoji</option>
|
|
<option value="pos_poison">Position Poison</option>
|
|
<option value="pki_poison">PKI Poison</option>
|
|
<option value="pki_dupe">PKI Duplication</option>
|
|
<option value="ddos">DDoS</option>
|
|
<option value="waypoint_flood">Waypoint Flood</option>
|
|
</select>
|
|
<p class="note">Note: A Target Node ID of <code>!ffffffff</code> means the attack will cycle through all
|
|
seen nodes.</p>
|
|
|
|
<div id="params-node_flood" class="param-group">
|
|
<p>Spams new Meshtastic nodes with coordinates too, filling nodedb-s.</p>
|
|
<div class="coord-grid">
|
|
<div><label for="flood-min-lat">Min Latitude</label><input type="number" id="flood-min-lat"
|
|
value="47.0"></div>
|
|
<div><label for="flood-max-lat">Max Latitude</label><input type="number" id="flood-max-lat"
|
|
value="48.0"></div>
|
|
<div><label for="flood-min-lon">Min Longitude</label><input type="number" id="flood-min-lon"
|
|
value="18.0"></div>
|
|
<div><label for="flood-max-lon">Max Longitude</label><input type="number" id="flood-max-lon"
|
|
value="20.0"></div>
|
|
</div>
|
|
<!-- <div> NOT WORKING WITH NEW CLIENT
|
|
<label for="flood-clientcrash">Crashclient on click</label>
|
|
<input type="checkbox" id="flood-clientcrash">
|
|
</div> -->
|
|
</div>
|
|
|
|
<div id="params-name_change" class="param-group">
|
|
<p>This appends the given string to longname of the selected node / random node. It is not saved.</p>
|
|
<label for="namechange-target-id">Target Node ID</label>
|
|
<div class="input-group">
|
|
<input type="text" id="namechange-target-id" value="!ffffffff">
|
|
<button type="button" class="btn-all">All</button>
|
|
</div>
|
|
<label for="emoji-input">Emoji/Text to Append</label>
|
|
<input type="text" id="emoji-input" value="😈" maxlength="30">
|
|
</div>
|
|
|
|
<div id="params-pos_poison" class="param-group">
|
|
<p>Randomizes a selected (or randomly selected) node's position.</p>
|
|
<label for="pospoison-target-id">Target Node ID</label>
|
|
<div class="input-group">
|
|
<input type="text" id="pospoison-target-id" value="!ffffffff">
|
|
<button type="button" class="btn-all">All</button>
|
|
</div>
|
|
<div class="coord-grid">
|
|
<div><label for="min-lat">Min Latitude</label><input type="number" id="min-lat" value="47.0"></div>
|
|
<div><label for="max-lat">Max Latitude</label><input type="number" id="max-lat" value="48.0"></div>
|
|
<div><label for="min-lon">Min Longitude</label><input type="number" id="min-lon" value="18.0"></div>
|
|
<div><label for="max-lon">Max Longitude</label><input type="number" id="max-lon" value="20.0"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="params-pki_poison" class="param-group">
|
|
<p>Randomizes a selected (or randomly selected) node's PKI, marking it red in other nodes' list.
|
|
</p>
|
|
<label for="pkipoison-target-id">Target Node ID</label>
|
|
<div class="input-group">
|
|
<input type="text" id="pkipoison-target-id" value="!ffffffff">
|
|
<button type="button" class="btn-all">All</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="params-pki_dupe" class="param-group">
|
|
<p>Copied PKI of a selected (or randomly selected) node, alerting it the key has exposed.
|
|
</p>
|
|
<label for="pkidupe-target-id">Target Node ID</label>
|
|
<div class="input-group">
|
|
<input type="text" id="pkidupe-target-id" value="!ffffffff">
|
|
<button type="button" class="btn-all">All</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="params-waypoint_flood" class="param-group">
|
|
<p>Floods the network with waypoints from a selected (or randomly selected) node.</p>
|
|
<label for="waypointflood-target-id">Source Node ID</label>
|
|
<div class="input-group">
|
|
<input type="text" id="waypoint_flood-target-id" value="!ffffffff">
|
|
<button type="button" class="btn-all">All</button>
|
|
</div>
|
|
<div class="coord-grid">
|
|
<div><label for="min-lat">Min Latitude</label><input type="number" id="min-lat" value="47.0"></div>
|
|
<div><label for="max-lat">Max Latitude</label><input type="number" id="max-lat" value="48.0"></div>
|
|
<div><label for="min-lon">Min Longitude</label><input type="number" id="min-lon" value="18.0"></div>
|
|
<div><label for="max-lon">Max Longitude</label><input type="number" id="max-lon" value="20.0"></div>
|
|
</div>
|
|
<div class="input-group">
|
|
<label for="flood-clientcrash">Crash client on map view</label>
|
|
<input type="checkbox" id="flood-clientcrash">
|
|
</div>
|
|
</div>
|
|
|
|
<label for="autostart-attack">Autostart Attack on boot</label>
|
|
<input type="checkbox" id="autostart-attack" title="Autostart the last selected attack on boot.">
|
|
|
|
<button id="start-attack-btn">Start Selected Attack</button>
|
|
<button id="stop-attack-btn">Stop Current Attack</button>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>Send Message As (One-Time)</h2>
|
|
<label for="source-node-id">Source Node ID</label>
|
|
<input type="text" id="source-node-id" placeholder="e.g., !a1b2c3d4">
|
|
<label for="message-text">Message</label>
|
|
<textarea id="message-text" placeholder="Enter your message here..."></textarea>
|
|
<button id="send-message-btn">Send Message</button>
|
|
</div>
|
|
|
|
<div class="panel full-width">
|
|
<h2>Nodes Detected</h2>
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Node ID</th>
|
|
<th>Name</th>
|
|
<th>Position</th>
|
|
<th>Last Heard</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="node-list-body">
|
|
<tr>
|
|
<td colspan="4">Waiting for nodes to show up...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel full-width">
|
|
<h2>Debug Console</h2>
|
|
<div id="debug-log"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const allNodes = new Map();
|
|
|
|
const loraPresets = {
|
|
'long_fast': { bw: 250.0, sf: 11, cr: 5, chanhash: 8 },
|
|
'long_slow': { bw: 125.0, sf: 12, cr: 8, chanhash: 8 },
|
|
'long_moderate': { bw: 125.0, sf: 11, cr: 8, chanhash: 8 },
|
|
'medium_slow': { bw: 250.0, sf: 10, cr: 5, chanhash: 8 },
|
|
'medium_fast': { bw: 250.0, sf: 9, cr: 5, chanhash: 31 },
|
|
'short_slow': { bw: 250.0, sf: 8, cr: 5, chanhash: 8 },
|
|
'short_fast': { bw: 250.0, sf: 7, cr: 5, chanhash: 8 },
|
|
};
|
|
|
|
const statusValue = document.getElementById('status-value');
|
|
const nodeListBody = document.getElementById('node-list-body');
|
|
const debugLog = document.getElementById('debug-log');
|
|
const attackSelector = document.getElementById('attack-selector');
|
|
const startAttackBtn = document.getElementById('start-attack-btn');
|
|
const stopAttackBtn = document.getElementById('stop-attack-btn');
|
|
const autostartAttack = document.getElementById('autostart-attack');
|
|
const sendMessageBtn = document.getElementById('send-message-btn');
|
|
const applyConfigBtn = document.getElementById('apply-config-btn');
|
|
const saveWifiBtn = document.getElementById('save-wifi-btn');
|
|
const loraPresetSelector = document.getElementById('lora-preset-selector');
|
|
const loraBw = document.getElementById('lora-bw');
|
|
const loraSf = document.getElementById('lora-sf');
|
|
const loraCr = document.getElementById('lora-cr');
|
|
|
|
let socket;
|
|
|
|
function initWebSocket() {
|
|
const gateway = `ws://${window.location.hostname}/ws`;
|
|
socket = new WebSocket(gateway);
|
|
|
|
socket.onopen = () => {
|
|
logToDebug("🔗 Connected to DarkMesh ESP32.");
|
|
sendWebSocketMessage({ action: 'initme' });
|
|
};
|
|
|
|
socket.onclose = () => {
|
|
logToDebug("⛔ Connection lost. Retrying in 3 seconds...");
|
|
setTimeout(initWebSocket, 3000);
|
|
};
|
|
|
|
socket.onerror = (error) => {
|
|
logToDebug(`❌ WebSocket Error`);
|
|
};
|
|
|
|
socket.onmessage = async (event) => {
|
|
let str = "";
|
|
try {
|
|
str = await event.data.text();
|
|
const data = JSON.parse(str);
|
|
handleWebSocketMessage(data);
|
|
} catch (e) {
|
|
logToDebug(`Received non-JSON message: ${str}`);
|
|
}
|
|
};
|
|
}
|
|
|
|
function handleWebSocketMessage(data) {
|
|
switch (data.type) {
|
|
case 'status_update':
|
|
const attackName = data.current_attack || 'none';
|
|
statusValue.textContent = data.current_attack || 'Idle';
|
|
autostartAttack.checked = data.autostart_attack || false;
|
|
if (attackSelector.value !== attackName) {
|
|
attackSelector.value = attackName;
|
|
attackSelector.dispatchEvent(new Event('change'));
|
|
}
|
|
break;
|
|
case 'node_update':
|
|
if (data.nodes && Array.isArray(data.nodes)) {
|
|
data.nodes.forEach(node => {
|
|
if (node.id) {
|
|
node.js_last_seen = Date.now();
|
|
allNodes.set(node.id, node);
|
|
}
|
|
});
|
|
updateNodeList();
|
|
}
|
|
break;
|
|
case 'debug':
|
|
logToDebug(data.message);
|
|
break;
|
|
case 'radio_config':
|
|
if (data.config) {
|
|
loraPresetSelector.value = 'custom';
|
|
document.getElementById('radio-freq').value = data.config.frequency || 869.525;
|
|
loraBw.value = data.config.bandwidth || 250.0;
|
|
loraSf.value = data.config.spreading_factor || 11;
|
|
loraCr.value = data.config.coding_rate || 5;
|
|
document.getElementById('radio-chanhash').value = data.config.chanhash || 8;
|
|
document.getElementById('radio-power').value = data.config.power || 20;
|
|
break;
|
|
}
|
|
case 'wifi_sta_config':
|
|
if (data.config) {
|
|
document.getElementById('wifi-ssid').value = data.config.ssid || '';
|
|
document.getElementById('wifi-password').value = data.config.password || '';
|
|
}
|
|
break;
|
|
default:
|
|
logToDebug(`Unknown message type: ${data.type}`);
|
|
}
|
|
}
|
|
|
|
function timeAgo(timestamp) {
|
|
const now = Date.now();
|
|
const seconds = Math.floor((now - timestamp) / 1000);
|
|
if (seconds < 10) return "Just now";
|
|
if (seconds < 60) return `${seconds}s ago`;
|
|
const minutes = Math.floor(seconds / 60);
|
|
if (minutes < 60) return `${minutes}m ago`;
|
|
const hours = Math.floor(minutes / 60);
|
|
return `${hours}h ago`;
|
|
}
|
|
|
|
function updateNodeList() {
|
|
const nodesArray = Array.from(allNodes.values());
|
|
nodesArray.sort((a, b) => b.js_last_seen - a.js_last_seen);
|
|
nodeListBody.innerHTML = '';
|
|
if (nodesArray.length > 0) {
|
|
nodesArray.forEach(node => {
|
|
const row = document.createElement('tr');
|
|
const lat = node.pos ? (node.pos.lat / 1e7).toFixed(4) : 'N/A';
|
|
const lon = node.pos ? (node.pos.lon / 1e7).toFixed(4) : 'N/A';
|
|
row.innerHTML = `
|
|
<td>${node.id || 'N/A'}</td>
|
|
<td>${node.name || 'N/A'}</td>
|
|
<td>${node.pos ? `${lat}, ${lon}` : 'N/A'}</td>
|
|
<td>${timeAgo(node.js_last_seen)}</td>
|
|
`;
|
|
nodeListBody.appendChild(row);
|
|
});
|
|
} else {
|
|
if (nodeListBody.innerHTML === '') {
|
|
nodeListBody.innerHTML = '<tr><td colspan="4">Waiting for nodes to show up...</td></tr>';
|
|
}
|
|
}
|
|
}
|
|
|
|
function logToDebug(message) {
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
debugLog.innerHTML += `[${timestamp}] ${message}\n`;
|
|
debugLog.scrollTop = debugLog.scrollHeight;
|
|
}
|
|
|
|
attackSelector.addEventListener('change', () => {
|
|
document.querySelectorAll('.param-group').forEach(group => group.classList.remove('active'));
|
|
const selectedAttack = attackSelector.value;
|
|
const paramGroup = document.getElementById(`params-${selectedAttack}`);
|
|
if (paramGroup) {
|
|
paramGroup.classList.add('active');
|
|
}
|
|
});
|
|
|
|
loraPresetSelector.addEventListener('change', () => {
|
|
const preset = loraPresetSelector.value;
|
|
if (preset in loraPresets) {
|
|
const values = loraPresets[preset];
|
|
loraBw.value = values.bw;
|
|
loraSf.value = values.sf;
|
|
loraCr.value = values.cr;
|
|
document.getElementById('radio-chanhash').value = values.chanhash;
|
|
}
|
|
});
|
|
|
|
[loraBw, loraSf, loraCr].forEach(input => {
|
|
input.addEventListener('input', () => {
|
|
loraPresetSelector.value = 'custom';
|
|
});
|
|
});
|
|
|
|
function sendWebSocketMessage(data) {
|
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
socket.send(JSON.stringify(data));
|
|
} else {
|
|
logToDebug("❌ WebSocket not connected. Cannot send command.");
|
|
}
|
|
}
|
|
|
|
startAttackBtn.addEventListener('click', () => {
|
|
const attack = attackSelector.value;
|
|
if (attack === 'none') {
|
|
alert('Please select an attack first.');
|
|
return;
|
|
}
|
|
const command = { action: 'start_attack', attack_type: attack, params: {} };
|
|
|
|
command.params.interval = parseInt(document.getElementById('attack-interval').value, 10);
|
|
command.params.mqtt = document.getElementById('attack-mqtt').checked ? 1 : 0;
|
|
command.params.autostart = document.getElementById('autostart-attack').checked ? 1 : 0;
|
|
|
|
if (attack === 'node_flood') {
|
|
command.params.min_lat = parseFloat(document.getElementById('flood-min-lat').value);
|
|
command.params.max_lat = parseFloat(document.getElementById('flood-max-lat').value);
|
|
command.params.min_lon = parseFloat(document.getElementById('flood-min-lon').value);
|
|
command.params.max_lon = parseFloat(document.getElementById('flood-max-lon').value);
|
|
command.params.crashclient = 0; //document.getElementById('flood-clientcrash').checked ? 1 : 0; //not working with new clients
|
|
} else if (attack === 'name_change') {
|
|
command.params.target_id = document.getElementById('namechange-target-id').value;
|
|
command.params.emoji = document.getElementById('emoji-input').value;
|
|
} else if (attack === 'pos_poison') {
|
|
command.params.target_id = document.getElementById('pospoison-target-id').value;
|
|
command.params.min_lat = parseFloat(document.getElementById('min-lat').value);
|
|
command.params.max_lat = parseFloat(document.getElementById('max-lat').value);
|
|
command.params.min_lon = parseFloat(document.getElementById('min-lon').value);
|
|
command.params.max_lon = parseFloat(document.getElementById('max-lon').value);
|
|
} else if (attack === 'pki_poison') {
|
|
command.params.target_id = document.getElementById('pkipoison-target-id').value;
|
|
} else if (attack === 'pki_dupe') {
|
|
command.params.target_id = document.getElementById('pkidupe-target-id').value;
|
|
} else if (attack === 'waypoint_flood') {
|
|
command.params.target_id = document.getElementById('waypoint_flood-target-id').value;
|
|
command.params.min_lat = parseFloat(document.getElementById('min-lat').value);
|
|
command.params.max_lat = parseFloat(document.getElementById('max-lat').value);
|
|
command.params.min_lon = parseFloat(document.getElementById('min-lon').value);
|
|
command.params.max_lon = parseFloat(document.getElementById('max-lon').value);
|
|
command.params.crashclient = document.getElementById('flood-clientcrash').checked ? 1 : 0;
|
|
}
|
|
|
|
logToDebug(`▶️ Starting attack: ${attack}`);
|
|
sendWebSocketMessage(command);
|
|
});
|
|
|
|
stopAttackBtn.addEventListener('click', () => {
|
|
logToDebug("⏹️ Stopping current attack.");
|
|
sendWebSocketMessage({ action: 'stop_attack' });
|
|
});
|
|
|
|
sendMessageBtn.addEventListener('click', () => {
|
|
const sourceId = document.getElementById('source-node-id').value;
|
|
const message = document.getElementById('message-text').value;
|
|
if (!sourceId || !message) {
|
|
alert('Source Node ID and message cannot be empty.');
|
|
return;
|
|
}
|
|
logToDebug(`✉️ Sending message as ${sourceId}`);
|
|
sendWebSocketMessage({ action: 'send_message', source_id: sourceId, message: message });
|
|
});
|
|
|
|
applyConfigBtn.addEventListener('click', () => {
|
|
const config = {
|
|
action: 'set_config',
|
|
params: {
|
|
frequency: parseFloat(document.getElementById('radio-freq').value),
|
|
bandwidth: parseFloat(loraBw.value),
|
|
spreading_factor: parseInt(loraSf.value),
|
|
coding_rate: parseInt(loraCr.value),
|
|
power: parseInt(document.getElementById('radio-power').value),
|
|
chanhash: parseInt(document.getElementById('radio-chanhash').value)
|
|
}
|
|
};
|
|
logToDebug(`⚙️ Applying new radio configuration...`);
|
|
sendWebSocketMessage(config);
|
|
});
|
|
|
|
|
|
saveWifiBtn.addEventListener('click', () => {
|
|
const ssid = document.getElementById('wifi-ssid').value.trim();
|
|
const password = document.getElementById('wifi-password').value;
|
|
|
|
if (!ssid) {
|
|
alert('SSID cannot be empty.');
|
|
return;
|
|
}
|
|
|
|
logToDebug(`📶 Saving WiFi STA configuration for "${ssid}"...`);
|
|
|
|
sendWebSocketMessage({
|
|
action: 'set_wifi_sta',
|
|
params: {
|
|
ssid,
|
|
password
|
|
}
|
|
});
|
|
});
|
|
|
|
nodeListBody.addEventListener('click', (e) => {
|
|
if (e.target.tagName === 'TD' && e.target.cellIndex === 0) {
|
|
const nodeId = e.target.textContent;
|
|
if (nodeId && nodeId !== 'N/A') {
|
|
document.getElementById('namechange-target-id').value = nodeId;
|
|
document.getElementById('pospoison-target-id').value = nodeId;
|
|
document.getElementById('pkipoison-target-id').value = nodeId;
|
|
document.getElementById('pkidupe-target-id').value = nodeId;
|
|
document.getElementById('source-node-id').value = nodeId;
|
|
logToDebug(`📋 Copied Node ID to all fields: ${nodeId}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('btn-all')) {
|
|
const inputField = e.target.previousElementSibling;
|
|
if (inputField && inputField.tagName === 'INPUT') {
|
|
inputField.value = '!ffffffff';
|
|
}
|
|
}
|
|
});
|
|
|
|
initWebSocket();
|
|
|
|
setInterval(() => {
|
|
if (allNodes.size > 0) {
|
|
updateNodeList();
|
|
}
|
|
}, 30000);
|
|
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html> |