codebook / potato /templates /admin_login.html
davidjurgens's picture
Deploy: Potato — Codebook Annotation
aceb1b2 verified
Raw
History Blame Contribute Delete
7.24 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }} - Admin Access</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<style>
.admin-login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--light-bg);
}
.admin-login-card {
background: white;
border-radius: var(--radius);
box-shadow: var(--box-shadow);
padding: 2rem;
width: 100%;
max-width: 400px;
text-align: center;
}
.admin-login-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--heading-color);
margin-bottom: 1rem;
}
.admin-login-description {
color: var(--text-color);
margin-bottom: 2rem;
line-height: 1.6;
}
.admin-login-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.admin-login-input {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: 0.875rem;
transition: var(--transition);
}
.admin-login-input:focus {
outline: none;
border-color: var(--ring);
box-shadow: 0 0 0 2px rgba(110, 86, 207, 0.25);
}
.admin-login-button {
background-color: var(--primary);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: var(--radius);
font-weight: 500;
cursor: pointer;
transition: var(--transition);
}
.admin-login-button:hover {
background-color: var(--secondary-color);
}
.admin-login-error {
color: var(--destructive);
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid var(--destructive);
border-radius: var(--radius);
padding: 0.75rem;
margin-top: 1rem;
display: none;
text-align: left;
font-size: 0.875rem;
}
.admin-login-error.show {
display: block;
}
.admin-login-hint {
color: var(--muted-foreground);
font-size: 0.75rem;
text-align: left;
margin: -0.5rem 0 0 0;
}
.admin-login-button[aria-busy="true"] {
opacity: 0.7;
cursor: progress;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
</head>
<body>
<div class="admin-login-container">
<div class="admin-login-card">
<h1 class="admin-login-title"><span aria-hidden="true">🔐</span> Admin Access Required</h1>
<p class="admin-login-description">
This area requires administrator privileges. Enter your API key to access the admin dashboard.
</p>
<p class="admin-login-description" style="font-size: 0.85em; opacity: 0.8;">
Your admin key is saved to <code>admin_api_key.txt</code> in the task
directory (it is also printed in the server log at startup).
</p>
<form class="admin-login-form" id="adminLoginForm">
<label class="sr-only" for="apiKey">Admin API key</label>
<input
type="password"
id="apiKey"
name="apiKey"
placeholder="Admin API key"
class="admin-login-input"
aria-describedby="apiKeyHint"
autocomplete="current-password"
required
>
<p id="apiKeyHint" class="admin-login-hint">
The key is stored in <code>admin_api_key.txt</code> in your task directory.
</p>
<button type="submit" class="admin-login-button" id="adminLoginSubmit">
Access dashboard
</button>
</form>
<div class="admin-login-error" id="errorMessage" role="alert" aria-live="polite"></div>
</div>
</div>
<script>
(function () {
const form = document.getElementById('adminLoginForm');
const submitBtn = document.getElementById('adminLoginSubmit');
const errorBox = document.getElementById('errorMessage');
const keyInput = document.getElementById('apiKey');
function showError(text) {
errorBox.textContent = text;
errorBox.classList.add('show');
}
function clearError() {
errorBox.textContent = '';
errorBox.classList.remove('show');
}
form.addEventListener('submit', function (e) {
e.preventDefault();
if (submitBtn.getAttribute('aria-busy') === 'true') return; // guard double-submit
clearError();
submitBtn.setAttribute('aria-busy', 'true');
submitBtn.disabled = true;
const originalLabel = submitBtn.textContent;
submitBtn.textContent = 'Verifying key…';
fetch('/admin', {
method: 'GET',
headers: { 'X-API-Key': keyInput.value }
})
.then(function (response) {
if (response.ok) {
window.location.href = '/admin';
return;
}
if (response.status === 401 || response.status === 403) {
showError("That API key wasn't recognized. Double-check the value in admin_api_key.txt.");
} else if (response.status === 429) {
showError("Too many sign-in attempts. Wait a moment and try again.");
} else if (response.status >= 500) {
showError("Something went wrong on the server. Try again in a moment.");
} else {
showError("Sign-in failed (HTTP " + response.status + "). Try again.");
}
})
.catch(function () {
showError("Couldn't reach the server. Check your connection and try again.");
})
.finally(function () {
submitBtn.removeAttribute('aria-busy');
submitBtn.disabled = false;
submitBtn.textContent = originalLabel;
});
});
})();
</script>
</body>
</html>