updated admin page with more functionality, simplified burger menu and added many-to-many parent-child relationship
Build and Push Docker Image / build-and-push (pull_request) Successful in 59s
Python Code Quality / python-code-quality (pull_request) Successful in 10s
Python Test / python-test (pull_request) Successful in 17s

This commit is contained in:
Brian Bjarke Jensen
2025-11-10 22:58:52 +01:00
parent df28af880a
commit 402b5898bb
26 changed files with 952 additions and 564 deletions
+349 -1
View File
@@ -185,6 +185,172 @@
background: #c82333;
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
}
.user-management h2::before {
content: "👥";
}
.search-box {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
margin-bottom: 15px;
}
.user-list {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
}
.user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
border-bottom: 1px solid #eee;
background: white;
}
.user-item:last-child {
border-bottom: none;
}
.user-item:hover {
background: #f8f9fa;
}
.user-info {
flex: 1;
}
.user-name {
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.user-meta {
font-size: 12px;
color: #666;
}
.admin-badge {
display: inline-block;
padding: 2px 8px;
background: #667eea;
color: white;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
margin-left: 8px;
}
.delete-user-btn {
padding: 6px 12px;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
transition: background 0.2s;
}
.delete-user-btn:hover {
background: #c82333;
}
.delete-user-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal.show {
display: flex;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 8px;
max-width: 500px;
width: 90%;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
.modal h3 {
color: #333;
margin-bottom: 15px;
}
.modal p {
color: #666;
margin-bottom: 20px;
line-height: 1.5;
}
.modal-buttons {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.modal-button {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.modal-button.cancel {
background: #6c757d;
color: white;
}
.modal-button.cancel:hover {
background: #5a6268;
}
.modal-button.confirm {
background: #dc3545;
color: white;
}
.modal-button.confirm:hover {
background: #c82333;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
</style>
</head>
<body>
@@ -218,9 +384,46 @@
<div id="error" class="error"></div>
</div>
<div class="section user-management">
<h2>User Management</h2>
<p style="color: #666; margin-bottom: 15px; font-size: 14px">
View and manage all users in the system.
</p>
<input
type="text"
id="userSearch"
class="search-box"
placeholder="Search users by username..."
oninput="filterUsers()"
/>
<div id="userListContainer" class="loading">Loading users...</div>
</div>
<button class="button logout-button" onclick="logout()">Logout</button>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal" id="deleteModal">
<div class="modal-content">
<h3>⚠️ Confirm User Deletion</h3>
<p id="deleteMessage"></p>
<p style="color: #dc3545; font-weight: 600">
This action cannot be undone. All associated children and their logs
(feedings, diapers, sleep) will be permanently deleted.
</p>
<div class="modal-buttons">
<button class="modal-button cancel" onclick="closeDeleteModal()">
Cancel
</button>
<button class="modal-button confirm" onclick="confirmDelete()">
Delete User
</button>
</div>
</div>
</div>
<script>
// Check if user is authenticated and is admin
const token = localStorage.getItem("access_token");
@@ -228,7 +431,10 @@
window.location.href = "/login.html";
}
// Verify user is admin
let allUsers = [];
let userToDelete = null;
// Verify user is admin and load data
fetch("/api/me", {
headers: {
Authorization: `Bearer ${token}`,
@@ -240,12 +446,154 @@
alert("Access denied. Admin privileges required.");
window.location.href = "/";
}
// Load users after verifying admin status
loadUsers();
})
.catch((error) => {
console.error("Error verifying admin status:", error);
window.location.href = "/login.html";
});
async function loadUsers() {
const container = document.getElementById("userListContainer");
try {
const response = await fetch("/api/admin/users", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error("Failed to load users");
}
allUsers = await response.json();
renderUsers(allUsers);
} catch (error) {
console.error("Error loading users:", error);
container.innerHTML = `
<div class="empty-state">
❌ Failed to load users: ${error.message}
</div>
`;
}
}
function renderUsers(users) {
const container = document.getElementById("userListContainer");
if (users.length === 0) {
container.innerHTML = `
<div class="empty-state">
No users found.
</div>
`;
return;
}
container.className = "user-list";
container.innerHTML = users
.map(
(user) => `
<div class="user-item">
<div class="user-info">
<div class="user-name">
${user.username}
${user.is_admin ? '<span class="admin-badge">ADMIN</span>' : ""}
</div>
<div class="user-meta">
ID: ${user.id} • Created: ${new Date(user.created_at).toLocaleDateString()}
</div>
</div>
<button
class="delete-user-btn"
onclick="showDeleteModal(${user.id}, '${user.username}', ${user.is_admin})"
${user.is_admin ? 'disabled title="Cannot delete admin users"' : ""}
>
Delete
</button>
</div>
`,
)
.join("");
}
function filterUsers() {
const searchTerm = document
.getElementById("userSearch")
.value.toLowerCase();
const filtered = allUsers.filter((user) =>
user.username.toLowerCase().includes(searchTerm),
);
renderUsers(filtered);
}
function showDeleteModal(userId, username, isAdmin) {
if (isAdmin) {
return; // Don't allow deleting admin users
}
userToDelete = { id: userId, username };
const modal = document.getElementById("deleteModal");
const message = document.getElementById("deleteMessage");
message.textContent = `Are you sure you want to delete user "${username}"?`;
modal.classList.add("show");
}
function closeDeleteModal() {
const modal = document.getElementById("deleteModal");
modal.classList.remove("show");
userToDelete = null;
}
async function confirmDelete() {
if (!userToDelete) return;
const modal = document.getElementById("deleteModal");
const confirmBtn = modal.querySelector(".modal-button.confirm");
confirmBtn.disabled = true;
confirmBtn.textContent = "Deleting...";
try {
const response = await fetch(`/api/admin/users/${userToDelete.id}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || "Failed to delete user");
}
const result = await response.json();
// Show success message
alert(
`${result.message}\n\n` +
`Deleted:\n` +
`${result.deleted_children.length} children\n` +
`${result.deleted_feedings} feedings\n` +
`${result.deleted_diapers} diaper changes\n` +
`${result.deleted_sleeps} sleep sessions`,
);
// Reload user list
await loadUsers();
closeDeleteModal();
} catch (error) {
console.error("Error deleting user:", error);
alert(`Failed to delete user: ${error.message}`);
} finally {
confirmBtn.disabled = false;
confirmBtn.textContent = "Delete User";
}
}
async function generateLink() {
const btn = document.getElementById("generateBtn");
const linkContainer = document.getElementById("linkContainer");