Files
Raccoon-TimeKeeper/templates/admin_users.html

279 lines
11 KiB
HTML
Raw Permalink 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.
{% extends "base.html" %}
{% block title %}User Management - Raccoon Timekeeper{% endblock %}
{% block tagline %}Manage users{% endblock %}
{% block content %}
<section class="card">
<div class="card-header">
<h2>User Management</h2>
<button class="btn btn-primary" onclick="openAddUserModal()">
<span class="btn-icon">+</span>
Add User
</button>
</div>
<div id="usersList" class="users-list">
<div class="loading">Loading users...</div>
</div>
</section>
<!-- Add User Modal -->
<div id="addUserModal" class="modal">
<div class="modal-content modal-large">
<div class="modal-header">
<h3>Add New User</h3>
<button class="modal-close" onclick="closeModal('addUserModal')">×</button>
</div>
<div class="modal-body">
<div class="form-grid">
<div class="form-group">
<label for="newUsername">Username *</label>
<input type="text" id="newUsername" placeholder="Username (min 3 chars)" minlength="3">
</div>
<div class="form-group">
<label for="newEmail">Email *</label>
<input type="email" id="newEmail" placeholder="user@example.com">
</div>
<div class="form-group">
<label for="newDisplayName">Display Name</label>
<input type="text" id="newDisplayName" placeholder="Display name (optional)">
</div>
<div class="form-group">
<label for="newPassword">Password *</label>
<input type="password" id="newPassword" placeholder="Min 8 characters" minlength="8">
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="newIsAdmin">
<span>Administrator</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('addUserModal')">Cancel</button>
<button class="btn btn-primary" onclick="saveNewUser()">Create User</button>
</div>
</div>
</div>
<!-- Edit User Modal -->
<div id="editUserModal" class="modal">
<div class="modal-content modal-large">
<div class="modal-header">
<h3>Edit User</h3>
<button class="modal-close" onclick="closeModal('editUserModal')">×</button>
</div>
<div class="modal-body">
<input type="hidden" id="editUserId">
<div class="form-grid">
<div class="form-group">
<label for="editUsername">Username</label>
<input type="text" id="editUsername" disabled>
</div>
<div class="form-group">
<label for="editEmail">Email</label>
<input type="email" id="editEmail" placeholder="user@example.com">
</div>
<div class="form-group">
<label for="editDisplayName">Display Name</label>
<input type="text" id="editDisplayName" placeholder="Display name">
</div>
<div class="form-group">
<label for="editPassword">New Password</label>
<input type="password" id="editPassword" placeholder="Leave blank to keep current">
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="editIsAdmin">
<span>Administrator</span>
</label>
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="editIsActive">
<span>Active</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger" onclick="deleteUser()">Delete User</button>
<button class="btn btn-secondary" onclick="closeModal('editUserModal')">Cancel</button>
<button class="btn btn-primary" onclick="saveEditUser()">Save Changes</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
const currentUserId = {{ current_user.id }};
document.addEventListener('DOMContentLoaded', loadUsers);
async function loadUsers() {
try {
const response = await fetch('/api/admin/users');
const users = await response.json();
renderUsers(users);
} catch (error) {
showToast('Error loading users', 'error');
}
}
function renderUsers(users) {
const container = document.getElementById('usersList');
let html = '<table class="data-table"><thead><tr>' +
'<th>User</th><th>Email</th><th>Role</th><th>Status</th><th>Last Login</th><th></th>' +
'</tr></thead><tbody>';
users.forEach(user => {
const lastLogin = user.last_login ? new Date(user.last_login).toLocaleDateString() : 'Never';
const isCurrentUser = user.id === currentUserId;
html += `<tr class="${!user.is_active ? 'inactive' : ''}">
<td>
<div class="user-cell">
<span class="user-avatar">${(user.display_name || user.username)[0].toUpperCase()}</span>
<div>
<strong>${user.display_name || user.username}</strong>
<small>@${user.username}</small>
</div>
</div>
</td>
<td>${user.email}</td>
<td>${user.is_admin ? '<span class="badge admin-badge">Admin</span>' : '<span class="badge">User</span>'}</td>
<td>${user.is_active ? '<span class="status-active">Active</span>' : '<span class="status-inactive">Inactive</span>'}</td>
<td>${lastLogin}</td>
<td>
<button class="btn btn-icon" onclick='openEditUserModal(${JSON.stringify(user)})'>✏️</button>
</td>
</tr>`;
});
html += '</tbody></table>';
container.innerHTML = html;
}
function openAddUserModal() {
document.getElementById('addUserModal').classList.add('active');
document.getElementById('newUsername').value = '';
document.getElementById('newEmail').value = '';
document.getElementById('newDisplayName').value = '';
document.getElementById('newPassword').value = '';
document.getElementById('newIsAdmin').checked = false;
document.getElementById('newUsername').focus();
}
function openEditUserModal(user) {
document.getElementById('editUserId').value = user.id;
document.getElementById('editUsername').value = user.username;
document.getElementById('editEmail').value = user.email;
document.getElementById('editDisplayName').value = user.display_name || '';
document.getElementById('editPassword').value = '';
document.getElementById('editIsAdmin').checked = user.is_admin;
document.getElementById('editIsActive').checked = user.is_active;
document.getElementById('editUserModal').classList.add('active');
}
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
async function saveNewUser() {
const data = {
username: document.getElementById('newUsername').value.trim(),
email: document.getElementById('newEmail').value.trim(),
display_name: document.getElementById('newDisplayName').value.trim(),
password: document.getElementById('newPassword').value,
is_admin: document.getElementById('newIsAdmin').checked
};
if (!data.username || !data.email || !data.password) {
showToast('Fill in all required fields', 'error');
return;
}
try {
const response = await fetch('/api/admin/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showToast('User created!', 'success');
closeModal('addUserModal');
loadUsers();
} else {
showToast(result.message, 'error');
}
} catch (error) {
showToast('Error creating user', 'error');
}
}
async function saveEditUser() {
const id = document.getElementById('editUserId').value;
const data = {
email: document.getElementById('editEmail').value.trim(),
display_name: document.getElementById('editDisplayName').value.trim(),
is_admin: document.getElementById('editIsAdmin').checked,
is_active: document.getElementById('editIsActive').checked
};
const password = document.getElementById('editPassword').value;
if (password) data.password = password;
try {
const response = await fetch(`/api/admin/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showToast('User updated!', 'success');
closeModal('editUserModal');
loadUsers();
} else {
showToast(result.message, 'error');
}
} catch (error) {
showToast('Error updating user', 'error');
}
}
async function deleteUser() {
const id = document.getElementById('editUserId').value;
if (parseInt(id) === currentUserId) {
showToast('Cannot delete your own account', 'error');
return;
}
if (!confirm('Delete this user? All their data will be permanently removed.')) return;
try {
const response = await fetch(`/api/admin/users/${id}`, { method: 'DELETE' });
const result = await response.json();
if (result.success) {
showToast('User deleted', 'success');
closeModal('editUserModal');
loadUsers();
} else {
showToast(result.message, 'error');
}
} catch (error) {
showToast('Error deleting user', 'error');
}
}
</script>
{% endblock %}