Files
2026-02-20 17:50:40 +00:00

212 lines
7.1 KiB
C

#ifndef WEB_INDEX_H
#define WEB_INDEX_H
const char HTML_INDEX[] = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 CAN FD Logger</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a2e;
color: #eee;
line-height: 1.6;
}
.header {
background: #16213e;
padding: 1rem;
text-align: center;
border-bottom: 2px solid #e94560;
}
.header h1 { color: #e94560; font-size: 1.5rem; }
.nav {
background: #0f3460;
padding: 0.5rem;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.nav a {
color: #fff;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background 0.3s;
}
.nav a:hover, .nav a.active { background: #e94560; }
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.status-card {
background: #16213e;
padding: 1.5rem;
border-radius: 8px;
border-left: 4px solid #e94560;
}
.status-card h3 { color: #e94560; margin-bottom: 0.5rem; }
.status-value { font-size: 2rem; font-weight: bold; }
.btn {
background: #e94560;
color: #fff;
border: none;
padding: 1rem 2rem;
font-size: 1rem;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
margin: 0.5rem;
}
.btn:hover { background: #ff6b6b; }
.btn:disabled { background: #666; cursor: not-allowed; }
.btn-green { background: #00d9ff; }
.btn-green:hover { background: #00b8d4; }
.controls {
text-align: center;
margin: 2rem 0;
}
.log {
background: #16213e;
padding: 1rem;
border-radius: 8px;
height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 0.875rem;
}
.log-entry { margin-bottom: 0.25rem; }
.log-entry.error { color: #ff6b6b; }
.log-entry.success { color: #00d9ff; }
</style>
</head>
<body>
<div class="header">
<h1>ESP32 CAN FD Logger</h1>
</div>
<nav class="nav">
<a href="/" class="active">Dashboard</a>
<a href="/graph">Graph</a>
<a href="/files">Files</a>
<a href="/can">CAN Transmit</a>
<a href="/settings">Settings</a>
<a href="/test">Test</a>
</nav>
<div class="container">
<div class="status-grid">
<div class="status-card">
<h3>WiFi Status</h3>
<div class="status-value" id="wifiStatus">AP Mode</div>
<div id="wifiIP">192.168.4.1</div>
</div>
<div class="status-card">
<h3>CAN Status</h3>
<div class="status-value" id="canStatus">Active</div>
<div id="canStats">RX: 0 | TX: 0</div>
</div>
<div class="status-card">
<h3>SD Card</h3>
<div class="status-value" id="sdStatus">OK</div>
<div id="sdSpace">-- MB free</div>
</div>
<div class="status-card">
<h3>Logging</h3>
<div class="status-value" id="logStatus">Stopped</div>
<div id="logFile">--</div>
</div>
</div>
<div class="controls">
<button class="btn btn-green" id="btnStart" onclick="startLogging()">Start Logging</button>
<button class="btn" id="btnStop" onclick="stopLogging()" disabled>Stop Logging</button>
</div>
<div class="log" id="log"></div>
</div>
<script>
let ws = null;
let logging = false;
function log(msg, type='info') {
const logDiv = document.getElementById('log');
const entry = document.createElement('div');
entry.className = 'log-entry ' + type;
entry.textContent = new Date().toLocaleTimeString() + ' - ' + msg;
logDiv.appendChild(entry);
logDiv.scrollTop = logDiv.scrollHeight;
}
function connectWebSocket() {
const host = window.location.hostname;
const wsPort = 81;
ws = new WebSocket('ws://' + host + ':' + wsPort);
ws.onopen = () => log('WebSocket connected', 'success');
ws.onclose = () => {
log('WebSocket disconnected, reconnecting...', 'error');
setTimeout(connectWebSocket, 3000);
};
ws.onerror = (e) => log('WebSocket error', 'error');
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'status') updateStatus(data);
};
}
function updateStatus(data) {
document.getElementById('canStats').textContent =
`RX: ${data.rx} | TX: ${data.tx}`;
}
function startLogging() {
fetch('/api/logging/start', {method: 'POST'})
.then(r => r.json())
.then(data => {
logging = true;
document.getElementById('btnStart').disabled = true;
document.getElementById('btnStop').disabled = false;
document.getElementById('logStatus').textContent = 'Running';
log('Logging started', 'success');
});
}
function stopLogging() {
fetch('/api/logging/stop', {method: 'POST'})
.then(r => r.json())
.then(data => {
logging = false;
document.getElementById('btnStart').disabled = false;
document.getElementById('btnStop').disabled = true;
document.getElementById('logStatus').textContent = 'Stopped';
log('Logging stopped');
});
}
function loadStatus() {
fetch('/api/status')
.then(r => r.json())
.then(data => {
document.getElementById('wifiStatus').textContent =
data.ap ? 'AP Mode' : (data.sta ? 'STA Mode' : 'Off');
document.getElementById('wifiIP').textContent = data.ip;
});
}
connectWebSocket();
loadStatus();
log('Dashboard loaded');
</script>
</body>
</html>
)rawliteral";
#endif // WEB_INDEX_H