This commit is contained in:
retoor 2025-11-08 20:50:16 +01:00
parent 8472811913
commit 993b5bfbb6
2 changed files with 416 additions and 0 deletions

View File

@ -0,0 +1,261 @@
document.addEventListener('DOMContentLoaded', () => {
const userQuotaList = document.getElementById('user-quota-list');
const addnewUserBtn = document.getElementById('add-new-user-btn');
const addUserModal = document.getElementById('add-user-modal');
const editQuotaModal = document.getElementById('edit-quota-modal');
const viewDetailsModal = document.getElementById('view-details-modal');
const closeButtons = document.querySelectorAll('.modal .close-button');
const addUserForm = document.getElementById('add-user-form');
const editQuotaForm = document.getElementById('edit-quota-form');
const adduserMessage = document.getElementById('add-user-message');
const editQuotaMessage = document.getElementById('edit-quota-message');
const userDetailsContent = document.getElementById('user-details-content');
// Function to open a modal
function openModal(modal) {
modal.style.display = 'block';
}
// Function to close a modal
function closeModal(modal) {
modal.style.display = 'none';
}
// Close modals when clicking on the close button
closeButtons.forEach(button => {
button.addEventListener('click', (event) => {
closeModal(event.target.closest('.modal'));
});
});
// Close modals when clicking outside the modal content
window.addEventListener('click', (event) => {
if (event.target === addUserModal) {
closeModal(addUserModal);
}
if (event.target === editQuotaModal) {
closeModal(editQuotaModal);
}
if (event.target === viewDetailsModal) {
closeModal(viewDetailsModal);
}
});
// Fetch and render users
async function fetchAndRenderUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
userQuotaList.innerHTML = ''; // Clear existing list
data.users.forEach(user => {
const quotaItem = document.createElement('div');
quotaItem.className = 'user-quota-item card';
const percentage = user.storage_quota_gb > 0 ? ((user.storage_used_gb / user.storage_quota_gb) * 100).toFixed(2) : 0;
quotaItem.innerHTML = `
<div class="user-info">
<h4>${user.full_name}</h4>
<p>${user.email}</p>
</div>
<div class="quota-details">
<div class="storage-gauge small">
<div class="storage-gauge-bar" style="width: ${percentage}%;"></div>
</div>
<p>${user.storage_used_gb} GB / ${user.storage_quota_gb} GB (${percentage}%)</p>
</div>
<div class="quota-actions">
<button class="btn-outline edit-quota-btn" data-email="${user.email}" data-quota="${user.storage_quota_gb}">Edit Quota</button>
<button class="btn-outline delete-user-btn" data-email="${user.email}">Delete User</button>
<button class="btn-outline view-details-btn" data-email="${user.email}">View Details</button>
${user.parent_email === null ? `<button class="btn-outline delete-team-btn" data-parent-email="${user.email}">Delete Team</button>` : ''}
</div>
`;
userQuotaList.appendChild(quotaItem);
});
// Attach event listeners to newly created buttons
attachButtonListeners();
} catch (error) {
console.error('Error fetching users:', error);
userQuotaList.innerHTML = '<p>Error loading user quotas.</p>';
}
}
function attachButtonListeners() {
// Edit Quota Buttons
document.querySelectorAll('.edit-quota-btn').forEach(button => {
button.addEventListener('click', (event) => {
const email = event.target.dataset.email;
const quota = event.target.dataset.quota;
document.getElementById('edit-quota-user-email').value = email;
document.getElementById('edit-quota-amount').value = quota;
editQuotaMessage.textContent = '';
openModal(editQuotaModal);
});
});
// Delete User Buttons
document.querySelectorAll('.delete-user-btn').forEach(button => {
button.addEventListener('click', async (event) => {
const email = event.target.dataset.email;
if (confirm(`Are you sure you want to delete user ${email}?`)) {
try {
const response = await fetch(`/api/users/${email}`, {
method: 'DELETE',
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
alert(`User ${email} deleted successfully.`);
fetchAndRenderUsers(); // Re-render the list
} catch (error) {
console.error('Error deleting user:', error);
alert(`Failed to delete user: ${error.message}`);
}
}
});
});
// View Details Buttons
document.querySelectorAll('.view-details-btn').forEach(button => {
button.addEventListener('click', async (event) => {
const email = event.target.dataset.email;
try {
const response = await fetch(`/api/users/${email}`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const data = await response.json();
const user = data.user;
userDetailsContent.innerHTML = `
<p><strong>Full Name:</strong> ${user.full_name}</p>
<p><strong>Email:</strong> ${user.email}</p>
<p><strong>Storage Used:</strong> ${user.storage_used_gb} GB</p>
<p><strong>Storage Quota:</strong> ${user.storage_quota_gb} GB</p>
<p><strong>Parent Email:</strong> ${user.parent_email || 'N/A'}</p>
`;
openModal(viewDetailsModal);
} catch (error) {
console.error('Error fetching user details:', error);
alert(`Failed to fetch user details: ${error.message}`);
}
});
});
// Delete Team Buttons
document.querySelectorAll('.delete-team-btn').forEach(button => {
button.addEventListener('click', async (event) => {
const parentEmail = event.target.dataset.parentEmail;
if (confirm(`Are you sure you want to delete the team managed by ${parentEmail}? This will delete all users created by this account.`)) {
try {
const response = await fetch(`/api/teams/${parentEmail}`, {
method: 'DELETE',
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
alert(`Team managed by ${parentEmail} and associated users deleted successfully.`);
fetchAndRenderUsers(); // Re-render the list
} catch (error) {
console.error('Error deleting team:', error);
alert(`Failed to delete team: ${error.message}`);
}
}
});
});
}
// Add New User Form Submission
addUserForm.addEventListener('submit', async (event) => {
event.preventDefault();
adduserMessage.textContent = ''; // Clear previous messages
const full_name = document.getElementById('new-user-full-name').value;
const email = document.getElementById('new-user-email').value;
const password = document.getElementById('new-user-password').value;
const confirm_password = document.getElementById('new-user-confirm-password').value;
if (password !== confirm_password) {
adduserMessage.textContent = 'Passwords do not match.';
return;
}
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ full_name, email, password }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to add user.');
}
alert(data.message);
closeModal(addUserModal);
addUserForm.reset();
fetchAndRenderUsers(); // Re-render the list
} catch (error) {
console.error('Error adding user:', error);
adduserMessage.textContent = error.message;
}
});
// Edit Quota Form Submission
editQuotaForm.addEventListener('submit', async (event) => {
event.preventDefault();
editQuotaMessage.textContent = ''; // Clear previous messages
const email = document.getElementById('edit-quota-user-email').value;
const new_quota_gb = parseFloat(document.getElementById('edit-quota-amount').value);
if (isNaN(new_quota_gb) || new_quota_gb <= 0) {
editQuotaMessage.textContent = 'Please enter a valid positive number for quota.';
return;
}
try {
const response = await fetch(`/api/users/${email}/quota`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ new_quota_gb }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to update quota.');
}
alert(data.message);
closeModal(editQuotaModal);
editQuotaForm.reset();
fetchAndRenderUsers(); // Re-render the list
} catch (error) {
console.error('Error updating quota:', error);
editQuotaMessage.textContent = error.message;
}
});
// Initial fetch and render
fetchAndRenderUsers();
// Event listener for "+ Add New User" button
addnewUserBtn.addEventListener('click', () => {
adduserMessage.textContent = ''; // Clear previous messages
addUserForm.reset();
openModal(addUserModal);
});
});

155
retoors/views/admin.py Normal file
View File

@ -0,0 +1,155 @@
from aiohttp import web
import aiohttp_jinja2
from aiohttp_session import get_session
from ..services.user_service import UserService
from ..models import QuotaUpdateModel, RegistrationModel
from typing import Dict, Any, List
import json
async def get_users(request: web.Request) -> web.Response:
user_service: UserService = request.app["user_service"]
session = await get_session(request)
current_user_email = session.get("user_email")
if not current_user_email:
return web.json_response({"error": "Unauthorized"}, status=401)
# For now, let's assume only the main user can see all users.
# In a real application, you'd have roles/permissions.
# The main user is the one who created the account.
# If the current user is the main user, they can see all users.
# Otherwise, they can only see users they created (their "team").
# This logic needs to be refined based on how "main user" is identified.
# For now, let's return all users for simplicity, assuming the logged-in user has admin-like access to this page.
# A more robust solution would involve checking if the current_user_email is the 'owner' of the site.
users = user_service.get_all_users()
# Filter out sensitive information like password and reset tokens
safe_users = []
for user in users:
safe_user = {k: v for k, v in user.items() if k not in ["password", "reset_token", "reset_token_expiry"]}
safe_users.append(safe_user)
return web.json_response({"users": safe_users})
async def add_user(request: web.Request) -> web.Response:
user_service: UserService = request.app["user_service"]
session = await get_session(request)
current_user_email = session.get("user_email")
if not current_user_email:
return web.json_response({"error": "Unauthorized"}, status=401)
try:
data = await request.json()
registration_data = RegistrationModel(**data)
# The current user is the parent of the new user
new_user = user_service.create_user(
full_name=registration_data.full_name,
email=registration_data.email,
password=registration_data.password,
parent_email=current_user_email # Assign current user as parent
)
safe_new_user = {k: v for k, v in new_user.items() if k not in ["password", "reset_token", "reset_token_expiry"]}
return web.json_response({"message": "User added successfully", "user": safe_new_user}, status=201)
except ValueError as e:
return web.json_response({"error": str(e)}, status=400)
except Exception as e:
return web.json_response({"error": "Invalid data provided", "details": str(e)}, status=400)
async def update_user_quota(request: web.Request) -> web.Response:
user_service: UserService = request.app["user_service"]
session = await get_session(request)
current_user_email = session.get("user_email")
target_user_email = request.match_info.get("email")
if not current_user_email:
return web.json_response({"error": "Unauthorized"}, status=401)
if not target_user_email:
return web.json_response({"error": "User email not provided"}, status=400)
# Ensure the current user has permission to update this user's quota
# For now, allow if current_user_email is the target_user_email or if target_user is a child of current_user
target_user = user_service.get_user_by_email(target_user_email)
if not target_user or (target_user_email != current_user_email and target_user.get("parent_email") != current_user_email):
return web.json_response({"error": "Forbidden: You do not have permission to update this user's quota"}, status=403)
try:
data = await request.json()
quota_update_data = QuotaUpdateModel(**data)
user_service.update_user_quota(target_user_email, quota_update_data.new_quota_gb)
return web.json_response({"message": f"Quota for {target_user_email} updated successfully"})
except ValueError as e:
return web.json_response({"error": str(e)}, status=400)
except Exception as e:
return web.json_response({"error": "Invalid data provided", "details": str(e)}, status=400)
async def delete_user(request: web.Request) -> web.Response:
user_service: UserService = request.app["user_service"]
session = await get_session(request)
current_user_email = session.get("user_email")
target_user_email = request.match_info.get("email")
if not current_user_email:
return web.json_response({"error": "Unauthorized"}, status=401)
if not target_user_email:
return web.json_response({"error": "User email not provided"}, status=400)
# Prevent a user from deleting themselves or a parent user
if target_user_email == current_user_email:
return web.json_response({"error": "Forbidden: You cannot delete your own account from this interface"}, status=403)
target_user = user_service.get_user_by_email(target_user_email)
if not target_user or target_user.get("parent_email") != current_user_email:
return web.json_response({"error": "Forbidden: You do not have permission to delete this user"}, status=403)
if user_service.delete_user(target_user_email):
return web.json_response({"message": f"User {target_user_email} deleted successfully"})
else:
return web.json_response({"error": "User not found or could not be deleted"}, status=404)
async def get_user_details(request: web.Request) -> web.Response:
user_service: UserService = request.app["user_service"]
session = await get_session(request)
current_user_email = session.get("user_email")
target_user_email = request.match_info.get("email")
if not current_user_email:
return web.json_response({"error": "Unauthorized"}, status=401)
if not target_user_email:
return web.json_response({"error": "User email not provided"}, status=400)
target_user = user_service.get_user_by_email(target_user_email)
if not target_user or (target_user_email != current_user_email and target_user.get("parent_email") != current_user_email):
return web.json_response({"error": "Forbidden: You do not have permission to view this user's details"}, status=403)
safe_user = {k: v for k, v in target_user.items() if k not in ["password", "reset_token", "reset_token_expiry"]}
return web.json_response({"user": safe_user})
async def delete_team(request: web.Request) -> web.Response:
user_service: UserService = request.app["user_service"]
session = await get_session(request)
current_user_email = session.get("user_email")
target_parent_email = request.match_info.get("parent_email")
if not current_user_email:
return web.json_response({"error": "Unauthorized"}, status=401)
if not target_parent_email:
return web.json_response({"error": "Parent email not provided"}, status=400)
# Only the parent user can delete their "team" (users they created)
if current_user_email != target_parent_email:
return web.json_response({"error": "Forbidden: You do not have permission to delete this team"}, status=403)
deleted_count = user_service.delete_users_by_parent_email(target_parent_email)
if deleted_count > 0:
return web.json_response({"message": f"Successfully deleted {deleted_count} users from the team managed by {target_parent_email}"})
else:
return web.json_response({"message": f"No users found for team managed by {target_parent_email} or could not be deleted"}, status=404)