user admin

This commit is contained in:
2026-08-27 15:57:48 +02:00
parent 84891adb89
commit ffd077bb4d
15 changed files with 765 additions and 24 deletions
+76 -2
View File
@@ -32,6 +32,13 @@ const elements = {
format: document.querySelector("#audio-format"),
source: document.querySelector("#audio-source"),
status: document.querySelector("#status"),
logout: document.querySelector("#logout"),
loginOverlay: document.querySelector("#login-overlay"),
loginForm: document.querySelector("#login-form"),
loginUsername: document.querySelector("#login-username"),
loginPassword: document.querySelector("#login-password"),
loginError: document.querySelector("#login-error"),
loginSubmit: document.querySelector("#login-submit"),
};
let state = null;
@@ -39,6 +46,14 @@ let seekBusy = false;
let draggedTrack = null;
let commandBusy = false;
class ApiError extends Error {
constructor(message, status, code) {
super(message);
this.status = status;
this.code = code;
}
}
function formatTime(value) {
if (!Number.isFinite(value) || value < 0) return "00:00:00";
const whole = Math.floor(value);
@@ -62,10 +77,34 @@ async function api(path, body) {
};
const response = await fetch(path, options);
const data = await response.json();
if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`);
if (!response.ok) {
throw new ApiError(data.error || `HTTP ${response.status}`, response.status, data.code);
}
return data;
}
function showLogin(message = null) {
if (message !== null) elements.loginError.textContent = message;
elements.loginOverlay.hidden = false;
window.setTimeout(() => elements.loginUsername.focus(), 0);
}
function hideLogin() {
elements.loginOverlay.hidden = true;
elements.loginError.textContent = "";
elements.loginPassword.value = "";
}
async function refreshAuth() {
try {
const auth = await api("/api/auth/status");
elements.logout.hidden = !auth.enabled || auth.local || !auth.authenticated;
if (auth.enabled && !auth.local && !auth.authenticated) showLogin();
} catch (error) {
setStatus(`Authenticatiestatus onbekend: ${error.message}`);
}
}
async function command(name, data = {}, pendingMessage = "") {
if (pendingMessage) setStatus(pendingMessage);
commandBusy = true;
@@ -441,10 +480,45 @@ async function refresh() {
if (commandBusy) return;
try {
render(await api("/api/state"));
hideLogin();
} catch (error) {
setStatus(`Geen verbinding: ${error.message}`);
if (error.code === "authentication-required") {
showLogin();
} else {
setStatus(`Geen verbinding: ${error.message}`);
}
}
}
elements.loginForm.addEventListener("submit", async (event) => {
event.preventDefault();
elements.loginSubmit.disabled = true;
elements.loginError.textContent = "";
try {
await api("/api/auth/login", {
username: elements.loginUsername.value,
password: elements.loginPassword.value,
});
hideLogin();
await refreshAuth();
await refresh();
} catch (error) {
showLogin(error.message);
elements.loginPassword.select();
} finally {
elements.loginSubmit.disabled = false;
}
});
elements.logout.addEventListener("click", async () => {
try {
await api("/api/auth/logout", {});
} finally {
elements.logout.hidden = true;
showLogin("Je bent uitgelogd.");
}
});
refreshAuth();
refresh();
setInterval(refresh, 1000);
+15
View File
@@ -19,6 +19,7 @@
<label for="renderer">UITVOER</label>
<select id="renderer"></select>
<button id="discover" class="square-button" type="button" title="Netwerkspelers zoeken" aria-label="Netwerkspelers zoeken"></button>
<button id="logout" class="text-button" type="button" hidden>UITLOGGEN</button>
</div>
</header>
@@ -129,6 +130,20 @@
</footer>
</main>
<section id="login-overlay" class="login-overlay" aria-labelledby="login-title" hidden>
<form id="login-form" class="login-panel">
<span class="brand-mark">RKT</span>
<p class="panel-kicker">WEB PLAYER</p>
<h1 id="login-title">Aanmelden</h1>
<label for="login-username">Gebruikersnaam</label>
<input id="login-username" name="username" autocomplete="username" required>
<label for="login-password">Wachtwoord</label>
<input id="login-password" name="password" type="password" autocomplete="current-password" required>
<p id="login-error" class="login-error" role="alert"></p>
<button id="login-submit" class="text-button" type="submit">AANMELDEN</button>
</form>
</section>
<script src="/app.js" defer></script>
</body>
</html>
+49
View File
@@ -198,6 +198,55 @@ input[type="range"] {
accent-color: var(--accent);
}
.login-overlay {
position: fixed;
z-index: 1000;
inset: 0;
display: grid;
place-items: center;
padding: 24px;
background: rgb(10 10 10 / 82%);
backdrop-filter: blur(8px);
}
.login-overlay[hidden] {
display: none;
}
.login-panel {
display: grid;
width: min(390px, 100%);
gap: 9px;
padding: 28px;
border: 1px solid var(--line);
background: var(--panel);
box-shadow: 0 24px 80px rgb(0 0 0 / 55%);
}
.login-panel h1 {
margin: 8px 0 14px;
}
.login-panel input {
min-width: 0;
margin-bottom: 8px;
padding: 10px 11px;
border: 1px solid var(--line);
color: var(--text);
background: #111;
}
.login-panel .text-button {
justify-self: start;
margin-top: 5px;
}
.login-error {
min-height: 1.3em;
margin: 0;
color: #ff796f;
}
.time-display {
display: flex;
gap: 5px;