331de4bc9e
- New settings page at /settings (localhost-only) accessible via tray icon - PIN manager with 6-digit codes, 5-min TTL, single-use - API token uses secrets.token_urlsafe(32) for 256-bit entropy - Config auto-creates config.yaml on first run with detected local IP - Default PIN and token generated randomly on first startup - Added pin_ttl setting (default 300s) - Updated build_exe.py for onefile exe with settings assets bundled - Added start.bat and start.sh for easy development startup
143 lines
4.5 KiB
HTML
143 lines
4.5 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>PaperDash Settings</title>
|
|
<link rel="stylesheet" href="settings.css" />
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>PaperDash Settings</h1>
|
|
<p class="subtitle">Changes take effect on next restart</p>
|
|
|
|
<form id="settings-form">
|
|
<div class="field">
|
|
<label for="dashboard-pin">Dashboard PIN</label>
|
|
<input
|
|
type="text"
|
|
id="dashboard-pin"
|
|
maxlength="4"
|
|
placeholder="****"
|
|
/>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="api-token">API Token</label>
|
|
<input
|
|
type="password"
|
|
id="api-token"
|
|
placeholder="Bearer token for scripts"
|
|
/>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="host">Host</label>
|
|
<input type="text" id="host" placeholder="Local IP address" />
|
|
<p class="hint">Address to bind the server to</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="port">Port</label>
|
|
<input
|
|
type="number"
|
|
id="port"
|
|
placeholder="8921"
|
|
min="1"
|
|
max="65535"
|
|
/>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="log-level">Log Level</label>
|
|
<select id="log-level">
|
|
<option value="DEBUG">DEBUG</option>
|
|
<option value="INFO">INFO</option>
|
|
<option value="WARNING">WARNING</option>
|
|
<option value="ERROR">ERROR</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button type="button" id="reset-btn">Reset</button>
|
|
<button type="submit" id="save-btn">Save</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div id="status" class="status"></div>
|
|
</div>
|
|
|
|
<script>
|
|
const form = document.getElementById("settings-form");
|
|
const statusEl = document.getElementById("status");
|
|
|
|
async function loadSettings() {
|
|
try {
|
|
const res = await fetch("/api/v1/settings");
|
|
if (!res.ok) throw new Error("Failed to load settings");
|
|
const data = await res.json();
|
|
document.getElementById("dashboard-pin").value =
|
|
data.dashboard_pin || "";
|
|
document.getElementById("api-token").value = data.api_token || "";
|
|
document.getElementById("host").value = data.host || "";
|
|
document.getElementById("port").value = data.port || 8921;
|
|
document.getElementById("log-level").value = data.log_level || "INFO";
|
|
} catch (err) {
|
|
showStatus("Failed to load settings: " + err.message, "error");
|
|
}
|
|
}
|
|
|
|
async function saveSettings(e) {
|
|
e.preventDefault();
|
|
const payload = {
|
|
dashboard_pin: document.getElementById("dashboard-pin").value.trim(),
|
|
api_token: document.getElementById("api-token").value.trim(),
|
|
host: document.getElementById("host").value.trim(),
|
|
port: parseInt(document.getElementById("port").value, 10),
|
|
log_level: document.getElementById("log-level").value,
|
|
};
|
|
|
|
try {
|
|
const res = await fetch("/api/v1/settings", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!res.ok) throw new Error("Failed to save settings");
|
|
showStatus("Settings saved. Restart the server to apply.", "success");
|
|
} catch (err) {
|
|
showStatus("Failed to save: " + err.message, "error");
|
|
}
|
|
}
|
|
|
|
function resetSettings() {
|
|
document.getElementById("dashboard-pin").value = "";
|
|
document.getElementById("api-token").value = "";
|
|
document.getElementById("host").value = "";
|
|
document.getElementById("port").value = 8921;
|
|
document.getElementById("log-level").value = "INFO";
|
|
hideStatus();
|
|
}
|
|
|
|
function showStatus(msg, type) {
|
|
statusEl.textContent = msg;
|
|
statusEl.className = "status " + type;
|
|
if (type === "success") {
|
|
setTimeout(hideStatus, 5000);
|
|
}
|
|
}
|
|
|
|
function hideStatus() {
|
|
statusEl.className = "status";
|
|
statusEl.style.display = "none";
|
|
}
|
|
|
|
form.addEventListener("submit", saveSettings);
|
|
document
|
|
.getElementById("reset-btn")
|
|
.addEventListener("click", resetSettings);
|
|
loadSettings();
|
|
</script>
|
|
</body>
|
|
</html>
|