Files
baby-monitor/src/baby_monitor/static/admin.html
T
Brian Bjarke Jensen 2b956213aa
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m6s
Python Code Quality / python-code-quality (pull_request) Successful in 10s
Python Test / python-test (pull_request) Successful in 15s
ruff and prettier fixes
2025-11-07 23:10:59 +01:00

320 lines
8.0 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>Baby Monitor - Admin</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 40px;
max-width: 600px;
width: 100%;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 14px;
}
.section {
margin-bottom: 30px;
}
.section h2 {
color: #444;
font-size: 18px;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.section h2::before {
content: "🔗";
margin-right: 8px;
}
.button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition:
transform 0.2s,
box-shadow 0.2s;
width: 100%;
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.button:active {
transform: translateY(0);
}
.button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.link-container {
display: none;
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border: 2px solid #667eea;
}
.link-container.show {
display: block;
}
.link-label {
font-size: 12px;
color: #666;
margin-bottom: 8px;
font-weight: 600;
text-transform: uppercase;
}
.link-display {
display: flex;
gap: 10px;
align-items: center;
}
.link-text {
flex: 1;
padding: 10px;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
font-family: "Courier New", monospace;
font-size: 13px;
word-break: break-all;
color: #333;
}
.copy-button {
padding: 10px 16px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
transition: background 0.2s;
white-space: nowrap;
}
.copy-button:hover {
background: #218838;
}
.copy-button.copied {
background: #155724;
}
.info-box {
background: #e7f3ff;
border-left: 4px solid #2196f3;
padding: 12px;
border-radius: 4px;
margin-top: 15px;
font-size: 14px;
color: #555;
}
.error {
background: #ffebee;
border-left: 4px solid #f44336;
color: #c62828;
padding: 12px;
border-radius: 4px;
margin-top: 15px;
display: none;
}
.error.show {
display: block;
}
.logout-button {
background: #dc3545;
padding: 8px 16px;
font-size: 14px;
margin-top: 20px;
}
.logout-button:hover {
background: #c82333;
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
}
</style>
</head>
<body>
<div class="container">
<h1>👨‍💼 Admin Dashboard</h1>
<p class="subtitle">Manage user invitations and settings</p>
<div class="section">
<h2>Generate Invitation Link</h2>
<p style="color: #666; margin-bottom: 15px; font-size: 14px">
Create a secure one-time link to invite a new user to register.
</p>
<button class="button" id="generateBtn" onclick="generateLink()">
Generate New Invitation Link
</button>
<div id="linkContainer" class="link-container">
<div class="link-label">Invitation Link</div>
<div class="link-display">
<div class="link-text" id="linkText"></div>
<button class="copy-button" id="copyBtn" onclick="copyLink()">
Copy
</button>
</div>
<div class="info-box">
️ This link expires in 24 hours and can only be used once. Share it
securely with the intended user.
</div>
</div>
<div id="error" class="error"></div>
</div>
<button class="button logout-button" onclick="logout()">Logout</button>
</div>
<script>
// Check if user is authenticated and is admin
const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Verify user is admin
fetch("/api/me", {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((response) => response.json())
.then((user) => {
if (!user.is_admin) {
alert("Access denied. Admin privileges required.");
window.location.href = "/";
}
})
.catch((error) => {
console.error("Error verifying admin status:", error);
window.location.href = "/login.html";
});
async function generateLink() {
const btn = document.getElementById("generateBtn");
const linkContainer = document.getElementById("linkContainer");
const linkText = document.getElementById("linkText");
const errorDiv = document.getElementById("error");
// Disable button and show loading state
btn.disabled = true;
btn.textContent = "Generating...";
errorDiv.classList.remove("show");
try {
const response = await fetch("/api/admin/generate-invitation", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`Failed to generate link: ${response.statusText}`);
}
const data = await response.json();
// Build the full URL
const baseUrl = window.location.origin;
const inviteUrl = `${baseUrl}/register.html?token=${data.token}`;
// Display the link
linkText.textContent = inviteUrl;
linkContainer.classList.add("show");
} catch (error) {
errorDiv.textContent = `Error: ${error.message}`;
errorDiv.classList.add("show");
} finally {
btn.disabled = false;
btn.textContent = "Generate New Invitation Link";
}
}
function copyLink() {
const linkText = document.getElementById("linkText").textContent;
const copyBtn = document.getElementById("copyBtn");
navigator.clipboard
.writeText(linkText)
.then(() => {
// Show success state
copyBtn.textContent = "✓ Copied!";
copyBtn.classList.add("copied");
// Reset after 2 seconds
setTimeout(() => {
copyBtn.textContent = "Copy";
copyBtn.classList.remove("copied");
}, 2000);
})
.catch((err) => {
alert("Failed to copy link: " + err);
});
}
function logout() {
localStorage.removeItem("access_token");
window.location.href = "/login.html";
}
</script>
</body>
</html>