Update.
This commit is contained in:
parent
8472811913
commit
993b5bfbb6
261
retoors/static/js/components/order.js
Normal file
261
retoors/static/js/components/order.js
Normal 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
155
retoors/views/admin.py
Normal 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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user