Files
led-controller/src/templates/settings.html

366 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LED Controller - Settings</title>
<link rel="stylesheet" href="/static/style.css">
<style>
.settings-container {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
overflow-y: auto;
height: 100%;
}
.settings-header {
margin-bottom: 2rem;
}
.settings-header h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.settings-header p {
color: #aaa;
}
.settings-section {
background-color: #1a1a1a;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid #4a4a4a;
}
.settings-section h2 {
font-size: 1.3rem;
margin-bottom: 1rem;
color: #fff;
border-bottom: 2px solid #4a4a4a;
padding-bottom: 0.5rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
color: #ccc;
font-weight: 500;
}
.form-group input[type="text"],
.form-group input[type="password"],
.form-group input[type="number"],
.form-group select {
width: 100%;
padding: 0.75rem;
background-color: #2e2e2e;
border: 1px solid #4a4a4a;
border-radius: 4px;
color: white;
font-size: 1rem;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: #5a5a5a;
}
.form-group small {
display: block;
margin-top: 0.25rem;
color: #888;
font-size: 0.875rem;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.status-info {
background-color: #2e2e2e;
border: 1px solid #4a4a4a;
border-radius: 4px;
padding: 1rem;
margin-bottom: 1rem;
}
.status-info h3 {
font-size: 1rem;
margin-bottom: 0.5rem;
color: #fff;
}
.status-info p {
color: #aaa;
margin: 0.25rem 0;
font-size: 0.9rem;
}
.status-connected {
color: #4caf50;
}
.status-disconnected {
color: #f44336;
}
.btn-group {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.btn-full {
flex: 1;
}
.back-link {
display: inline-block;
margin-bottom: 1rem;
color: #aaa;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.back-link:hover {
background-color: #2e2e2e;
color: white;
}
.message {
padding: 0.75rem;
border-radius: 4px;
margin-bottom: 1rem;
display: none;
}
.message.success {
background-color: #1b5e20;
color: #4caf50;
border: 1px solid #4caf50;
}
.message.error {
background-color: #5e1b1b;
color: #f44336;
border: 1px solid #f44336;
}
.message.show {
display: block;
}
</style>
</head>
<body>
<div class="app-container">
<div class="settings-container">
<a href="/" class="back-link">← Back to Dashboard</a>
<div class="settings-header">
<h1>Device Settings</h1>
<p>Configure WiFi Access Point and ESP-NOW options</p>
</div>
<div id="message" class="message"></div>
<!-- ESP-NOW (LED driver / bridge channel) -->
<div class="settings-section">
<h2>ESP-NOW</h2>
<form id="espnow-form">
<div class="form-group">
<label for="wifi-channel-page-input">WiFi channel (ESP-NOW)</label>
<input type="number" id="wifi-channel-page-input" name="wifi_channel" min="1" max="11" required>
<small>STA channel (111) for LED drivers and the serial bridge. Use the same value on every device.</small>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary btn-full">Save channel</button>
</div>
</form>
</div>
<!-- WiFi Access Point Settings -->
<div class="settings-section">
<h2>WiFi Access Point Settings</h2>
<div id="ap-status" class="status-info">
<h3>AP Status</h3>
<p>Loading...</p>
</div>
<form id="ap-form">
<div class="form-group">
<label for="ap-ssid">AP SSID (Network Name)</label>
<input type="text" id="ap-ssid" name="ssid" placeholder="Enter AP name" required>
<small>The name of the WiFi access point this device creates</small>
</div>
<div class="form-group">
<label for="ap-password">AP Password</label>
<input type="password" id="ap-password" name="password" placeholder="Enter AP password (min 8 chars)" data-bwignore>
<small>Leave empty for open network (min 8 characters if set)</small>
</div>
<div class="form-group">
<label for="ap-channel">Channel (1-11)</label>
<input type="number" id="ap-channel" name="channel" min="1" max="11" placeholder="Auto">
<small>WiFi channel (1-11 for 2.4GHz). Leave empty for auto.</small>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary btn-full">Configure AP</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Show message helper
function showMessage(text, type = 'success') {
const messageEl = document.getElementById('message');
messageEl.textContent = text;
messageEl.className = `message ${type} show`;
setTimeout(() => {
messageEl.classList.remove('show');
}, 5000);
}
async function loadEspnowChannel() {
try {
const response = await fetch('/settings');
const data = await response.json();
const chInput = document.getElementById('wifi-channel-page-input');
if (chInput && data && typeof data === 'object') {
const ch = data.wifi_channel;
chInput.value =
ch !== undefined && ch !== null && ch !== '' ? String(ch) : '6';
}
} catch (error) {
console.error('Error loading ESP-NOW channel:', error);
}
}
document.getElementById('espnow-form').addEventListener('submit', async (e) => {
e.preventDefault();
const chRaw = document.getElementById('wifi-channel-page-input').value;
const wifiChannel = parseInt(chRaw, 10);
if (Number.isNaN(wifiChannel) || wifiChannel < 1 || wifiChannel > 11) {
showMessage('WiFi channel must be between 1 and 11', 'error');
return;
}
try {
const response = await fetch('/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ wifi_channel: wifiChannel }),
});
const result = await response.json();
if (response.ok) {
showMessage('ESP-NOW channel saved.', 'success');
} else {
showMessage(`Error: ${result.error || 'Failed to save'}`, 'error');
}
} catch (error) {
showMessage(`Error: ${error.message}`, 'error');
}
});
// Load AP status and config
async function loadAPStatus() {
try {
const response = await fetch('/settings/wifi/ap');
const config = await response.json();
const statusEl = document.getElementById('ap-status');
if (config.active) {
statusEl.innerHTML = `
<h3>AP Status: <span class="status-connected">Active</span></h3>
<p><strong>SSID:</strong> ${config.ssid || 'N/A'}</p>
<p><strong>Channel:</strong> ${config.channel || 'Auto'}</p>
<p><strong>IP Address:</strong> ${config.ip || 'N/A'}</p>
`;
} else {
statusEl.innerHTML = `
<h3>AP Status: <span class="status-disconnected">Inactive</span></h3>
<p>Access Point is not currently active</p>
`;
}
// Load saved values
if (config.saved_ssid) document.getElementById('ap-ssid').value = config.saved_ssid;
if (config.saved_channel) document.getElementById('ap-channel').value = config.saved_channel;
} catch (error) {
console.error('Error loading AP status:', error);
}
}
// AP form submission
document.getElementById('ap-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = {
ssid: document.getElementById('ap-ssid').value,
password: document.getElementById('ap-password').value,
channel: document.getElementById('ap-channel').value || null
};
// Validate password length if provided
if (formData.password && formData.password.length > 0 && formData.password.length < 8) {
showMessage('AP password must be at least 8 characters', 'error');
return;
}
// Convert channel to number if provided
if (formData.channel) {
formData.channel = parseInt(formData.channel);
if (formData.channel < 1 || formData.channel > 11) {
showMessage('Channel must be between 1 and 11', 'error');
return;
}
}
try {
const response = await fetch('/settings/wifi/ap', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (response.ok) {
showMessage('Access Point configured successfully!', 'success');
setTimeout(loadAPStatus, 1000);
} else {
showMessage(`Error: ${result.error || 'Failed to configure AP'}`, 'error');
}
} catch (error) {
showMessage(`Error: ${error.message}`, 'error');
}
});
// Load all data on page load
loadEspnowChannel();
loadAPStatus();
// Refresh status every 10 seconds
setInterval(loadAPStatus, 10000);
</script>
</body>
</html>