Files
car-key-killer/safe-ui-polish/index.html
drjones 2385d0e099 CAR-KEY-KILLER: Complete jamming system with enhanced error handling and dark hacker documentation
- Validated all jamming functions line-by-line
- Added individual radio status tracking with error reporting
- Enhanced web interface with live status updates
- Rewrote README with dark/hacker aesthetic
- Added detailed effectiveness explanation (30-100m range)
- Fixed state management and error handling
- Tested compilation with PlatformIO
- Added comprehensive legal warnings
- Added .gitignore for build artifacts
2026-03-05 02:57:45 -08:00

498 lines
13 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Embedded Device Dashboard</title>
<style>
:root {
--bg: #0b1020;
--panel: #121a2f;
--panel-2: #0f1629;
--text: #e8edf7;
--muted: #a5b4cf;
--border: #283555;
--accent: #4f8cff;
--accent-2: #7aa6ff;
--good: #1f9d55;
--warn: #c78b19;
--bad: #d14343;
--shadow: 0 12px 28px rgba(0, 0, 0, 0.28);
--radius: 12px;
--radius-sm: 8px;
}
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
background: radial-gradient(1200px 700px at 20% -10%, #1a2550 0%, var(--bg) 50%);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
min-height: 100%;
}
.app {
max-width: 1080px;
margin: 0 auto;
padding: 20px;
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
h1 {
font-size: 1.35rem;
margin: 0;
letter-spacing: 0.2px;
}
.sub {
color: var(--muted);
margin-top: 4px;
font-size: 0.92rem;
}
.chip {
display: inline-flex;
align-items: center;
gap: 8px;
border: 1px solid var(--border);
border-radius: 999px;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.03);
color: var(--muted);
font-size: 0.88rem;
}
.dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: var(--warn);
box-shadow: 0 0 0 5px rgba(199, 139, 25, 0.18);
transition: all 180ms ease;
}
.dot.ok {
background: var(--good);
box-shadow: 0 0 0 5px rgba(31, 157, 85, 0.2);
}
.grid {
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: 16px;
}
@media (max-width: 900px) {
.grid {
grid-template-columns: 1fr;
}
}
.card {
background: linear-gradient(180deg, var(--panel), var(--panel-2));
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 16px;
}
.card h2 {
margin: 0 0 12px;
font-size: 1rem;
font-weight: 650;
}
.metrics {
display: grid;
grid-template-columns: repeat(3, minmax(120px, 1fr));
gap: 12px;
}
@media (max-width: 600px) {
.metrics {
grid-template-columns: 1fr 1fr;
}
}
.metric {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 12px;
}
.metric .label {
color: var(--muted);
font-size: 0.82rem;
margin-bottom: 6px;
}
.metric .value {
font-size: 1.05rem;
font-weight: 700;
letter-spacing: 0.2px;
}
.controls {
display: grid;
gap: 14px;
}
.field {
display: grid;
gap: 8px;
}
.field label {
font-size: 0.9rem;
color: var(--muted);
}
.row {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
input[type="range"] {
width: 100%;
accent-color: var(--accent);
}
button {
appearance: none;
border: 1px solid transparent;
border-radius: 10px;
padding: 10px 14px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
color: #fff;
background: var(--accent);
transition: transform 100ms ease, filter 150ms ease, border-color 150ms ease;
}
button:hover {
filter: brightness(1.06);
}
button:active {
transform: translateY(1px);
}
button.secondary {
background: rgba(255, 255, 255, 0.06);
border-color: var(--border);
color: var(--text);
}
button.critical {
background: var(--bad);
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
button:focus-visible,
input:focus-visible {
outline: 2px solid var(--accent-2);
outline-offset: 2px;
}
.status-pill {
display: inline-flex;
align-items: center;
gap: 8px;
border-radius: 999px;
padding: 7px 11px;
border: 1px solid var(--border);
background: rgba(255, 255, 255, 0.03);
font-size: 0.85rem;
margin-bottom: 6px;
}
pre {
margin: 0;
min-height: 280px;
max-height: 420px;
overflow: auto;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: #0c1222;
padding: 12px;
line-height: 1.4;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 0.8rem;
color: #d9e5ff;
white-space: pre-wrap;
word-break: break-word;
}
.footer {
margin-top: 14px;
color: var(--muted);
font-size: 0.8rem;
text-align: right;
}
.toasts {
position: fixed;
right: 14px;
bottom: 14px;
display: grid;
gap: 8px;
z-index: 50;
width: min(360px, calc(100vw - 28px));
}
.toast {
border-radius: 10px;
border: 1px solid var(--border);
background: #111b33;
color: var(--text);
padding: 10px 12px;
box-shadow: var(--shadow);
font-size: 0.88rem;
animation: slideIn 140ms ease;
}
.toast.ok { border-left: 4px solid var(--good); }
.toast.warn { border-left: 4px solid var(--warn); }
.toast.err { border-left: 4px solid var(--bad); }
@keyframes slideIn {
from { transform: translateY(8px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
</style>
</head>
<body>
<div class="app">
<div class="topbar">
<div>
<h1>Embedded Device Dashboard</h1>
<div class="sub">Polished, responsive, and API-ready web interface</div>
</div>
<div class="chip">
<span id="onlineDot" class="dot"></span>
<span id="onlineText">Connecting...</span>
</div>
</div>
<div class="grid">
<section class="card">
<h2>Live Status</h2>
<div id="statePill" class="status-pill">
<span class="dot" id="stateDot"></span>
<span id="stateText">Unknown</span>
</div>
<div class="metrics">
<div class="metric">
<div class="label">Uptime</div>
<div class="value" id="uptime">--</div>
</div>
<div class="metric">
<div class="label">Free Heap</div>
<div class="value" id="heap">--</div>
</div>
<div class="metric">
<div class="label">Power</div>
<div class="value" id="powerMetric">--</div>
</div>
</div>
</section>
<section class="card">
<h2>Controls</h2>
<div class="controls">
<div class="field">
<label for="powerSlider">Power Level: <span id="powerLabel">10</span></label>
<input id="powerSlider" type="range" min="0" max="10" step="1" value="10">
</div>
<div class="row">
<button id="toggleBtn">Toggle</button>
<button id="applyBtn" class="secondary">Apply Settings</button>
<button id="refreshBtn" class="secondary">Refresh</button>
</div>
</div>
</section>
</div>
<section class="card" style="margin-top: 16px;">
<h2>System Log</h2>
<pre id="logView">Loading logs...</pre>
<div class="footer">Auto-refresh every 1 second</div>
</section>
</div>
<div id="toasts" class="toasts" aria-live="polite" aria-atomic="true"></div>
<script>
const ui = {
onlineDot: document.getElementById("onlineDot"),
onlineText: document.getElementById("onlineText"),
stateDot: document.getElementById("stateDot"),
stateText: document.getElementById("stateText"),
uptime: document.getElementById("uptime"),
heap: document.getElementById("heap"),
powerMetric: document.getElementById("powerMetric"),
powerSlider: document.getElementById("powerSlider"),
powerLabel: document.getElementById("powerLabel"),
toggleBtn: document.getElementById("toggleBtn"),
applyBtn: document.getElementById("applyBtn"),
refreshBtn: document.getElementById("refreshBtn"),
logView: document.getElementById("logView"),
toasts: document.getElementById("toasts")
};
const state = {
enabled: false,
pollHandle: null,
inFlight: false
};
function showToast(message, kind = "ok", timeout = 2600) {
const el = document.createElement("div");
el.className = "toast " + kind;
el.textContent = message;
ui.toasts.appendChild(el);
window.setTimeout(() => {
el.remove();
}, timeout);
}
function setOnline(online) {
ui.onlineDot.classList.toggle("ok", online);
ui.onlineText.textContent = online ? "Device online" : "Device offline";
}
function fmtUptime(ms) {
const s = Math.max(0, Math.floor(ms / 1000));
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
if (h > 0) return h + "h " + m + "m " + sec + "s";
if (m > 0) return m + "m " + sec + "s";
return sec + "s";
}
function renderStatus(telemetry) {
state.enabled = Boolean(telemetry.jamming_enabled);
const active = state.enabled;
ui.stateDot.classList.toggle("ok", active);
ui.stateText.textContent = active ? "ACTIVE" : "INACTIVE";
ui.toggleBtn.textContent = active ? "Stop" : "Start";
ui.uptime.textContent = fmtUptime(Number(telemetry.uptime_ms || 0));
ui.heap.textContent = String(telemetry.free_heap ?? "--");
ui.powerMetric.textContent = String(telemetry.jam_power ?? "--") + " dBm";
ui.powerSlider.value = String(telemetry.jam_power ?? 0);
ui.powerLabel.textContent = ui.powerSlider.value;
}
async function fetchJson(path, options) {
const res = await fetch(path, options);
if (!res.ok) throw new Error(path + " failed with " + res.status);
return res.json();
}
async function fetchText(path, options) {
const res = await fetch(path, options);
if (!res.ok) throw new Error(path + " failed with " + res.status);
return res.text();
}
async function refreshAll(silent = false) {
if (state.inFlight) return;
state.inFlight = true;
try {
const [telemetry, logs] = await Promise.all([
fetchJson("/api/telemetry"),
fetchText("/api/log")
]);
renderStatus(telemetry);
ui.logView.textContent = logs || "(No log entries)";
ui.logView.scrollTop = ui.logView.scrollHeight;
setOnline(true);
} catch (err) {
setOnline(false);
if (!silent) showToast("Refresh failed: " + err.message, "err", 3200);
} finally {
state.inFlight = false;
}
}
async function onToggle() {
ui.toggleBtn.disabled = true;
try {
const out = await fetchJson("/api/toggle", { method: "POST" });
state.enabled = Boolean(out.enabled);
showToast(state.enabled ? "Started successfully" : "Stopped successfully");
await refreshAll(true);
} catch (err) {
showToast("Toggle failed: " + err.message, "err", 3200);
} finally {
ui.toggleBtn.disabled = false;
}
}
async function onApply() {
ui.applyBtn.disabled = true;
try {
const payload = { power: Number(ui.powerSlider.value) };
const out = await fetchJson("/api/settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (out && out.success) {
showToast("Settings applied");
await refreshAll(true);
} else {
throw new Error("settings response invalid");
}
} catch (err) {
showToast("Apply failed: " + err.message, "err", 3200);
} finally {
ui.applyBtn.disabled = false;
}
}
function bindEvents() {
ui.powerSlider.addEventListener("input", () => {
ui.powerLabel.textContent = ui.powerSlider.value;
});
ui.toggleBtn.addEventListener("click", onToggle);
ui.applyBtn.addEventListener("click", onApply);
ui.refreshBtn.addEventListener("click", () => refreshAll(false));
window.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") refreshAll(true);
});
}
async function main() {
bindEvents();
await refreshAll(true);
state.pollHandle = window.setInterval(() => refreshAll(true), 1000);
}
main().catch((err) => {
showToast("Startup failed: " + err.message, "err", 4000);
});
</script>
</body>
</html>