212 lines
7.1 KiB
C
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
|