Sửa đổi giao diện của quản lí người dùng
This commit is contained in:
@@ -97,13 +97,16 @@ router.get('/users', protect, async (req, res) => {
|
||||
router.put('/users/:id', protect, async (req, res) => {
|
||||
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') return res.status(403).json({ message: 'Forbidden' });
|
||||
try {
|
||||
const { fullName, email, role, password } = req.body;
|
||||
const { fullName, email, role, password, quota } = req.body;
|
||||
const user = await User.findById(req.params.id);
|
||||
if (!user) return res.status(404).json({ message: 'User not found' });
|
||||
if (fullName) user.fullName = fullName;
|
||||
if (email) user.email = email;
|
||||
if (role && user.role !== 'admin') user.role = role;
|
||||
if (password) user.password = password;
|
||||
if (quota !== undefined) {
|
||||
user.storage = { ...user.storage, quota: parseInt(quota) * 1024 * 1024 };
|
||||
}
|
||||
await user.save();
|
||||
res.json({ message: 'User updated' });
|
||||
} catch (error) { res.status(500).json({ message: error.message }); }
|
||||
|
||||
+111
-11
@@ -1173,8 +1173,9 @@ html, body {
|
||||
.admin-table th:nth-child(1) { min-width: 160px; } /* Họ tên */
|
||||
.admin-table th:nth-child(2) { min-width: 120px; } /* Username */
|
||||
.admin-table th:nth-child(3) { min-width: 200px; } /* Email */
|
||||
.admin-table th:nth-child(4) { min-width: 130px; } /* Quyền hạn */
|
||||
.admin-table th:nth-child(5) { min-width: 140px; } /* Reset Password */
|
||||
.admin-table th:nth-child(4) { min-width: 120px; } /* Quyền hạn */
|
||||
.admin-table th:nth-child(5) { min-width: 100px; } /* Dung lượng */
|
||||
.admin-table th:nth-child(6) { min-width: 140px; } /* Reset Password */
|
||||
.admin-table th:nth-child(6) { min-width: 140px; } /* Thao tác */
|
||||
|
||||
.admin-table td input, .admin-table td select {
|
||||
@@ -1204,28 +1205,127 @@ html, body {
|
||||
}
|
||||
|
||||
/* Admin User Management Header */
|
||||
.admin-management-header {
|
||||
.admin-management-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.cleanup-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.cleanup-btn {
|
||||
background: transparent; /* Nền trùng màu dashboard */
|
||||
color: #fff;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: #444444 !important;
|
||||
color: #222222 !important;
|
||||
padding: 0 20px !important;
|
||||
height: 36px;
|
||||
border: none !important;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: background 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cleanup-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1); /* Màu xám active của dashboard */
|
||||
background: #555555 !important;
|
||||
}
|
||||
|
||||
.admin-search-container {
|
||||
display: flex;
|
||||
gap: 0; /* Sát cạnh nhau */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-search-container input {
|
||||
flex: 1;
|
||||
background: #262626 !important;
|
||||
border: 1px solid #000000 !important;
|
||||
color: #ffffff !important;
|
||||
padding: 8px 15px !important;
|
||||
border-radius: 6px 0 0 6px !important;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.admin-search-btn {
|
||||
background: #444444 !important;
|
||||
color: #ffffff !important;
|
||||
border: 1px solid #000000 !important;
|
||||
padding: 0 25px !important;
|
||||
border-radius: 0 6px 6px 0 !important;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
/* Card layout styles */
|
||||
.admin-user-card {
|
||||
background: #262626 !important;
|
||||
border: 1px solid #404040 !important;
|
||||
border-radius: 12px !important;
|
||||
padding: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr 2fr 1fr 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.admin-users-header-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr 2fr 1fr 1fr 1fr 1fr;
|
||||
gap: 15px;
|
||||
padding: 0 16px 12px 16px;
|
||||
color: #a3a3a3;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-field input:not(:disabled), .card-field select:not(:disabled) {
|
||||
background: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
border: 1px solid #404040;
|
||||
border-radius: 4px;
|
||||
padding: 6px 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-field input:disabled, .card-field select:disabled {
|
||||
background: #404040 !important;
|
||||
color: #a3a3a3 !important;
|
||||
border: 1px solid #525252;
|
||||
border-radius: 4px;
|
||||
padding: 6px 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Màu sắc linh hoạt cho Input theo trạng thái */
|
||||
.card-field input[type="text"], .card-field input[type="email"], .card-field input[type="number"] {
|
||||
background: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
}
|
||||
|
||||
.card-field input:disabled, .card-field select:disabled {
|
||||
background: #404040 !important; /* Nền xám tối */
|
||||
color: #a3a3a3 !important; /* Chữ xám mờ */
|
||||
border-color: #525252 !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.card-field .edit-btn-small {
|
||||
background: #28a745 !important; /* Nền xanh lá */
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.admin-search-container {
|
||||
|
||||
+1
-11
@@ -140,17 +140,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-user-management" class="dashboard-tab-pane admin-only">
|
||||
<div class="admin-management-header">
|
||||
|
||||
<button class="cleanup-btn" onclick="openManualCleanupConfirm()">
|
||||
🧹 Dọn dẹp dữ liệu
|
||||
</button>
|
||||
</div>
|
||||
<div class="admin-search-container">
|
||||
<input type="text" id="admin-user-search-input" placeholder="Tìm kiếm theo tên, email, username..." onkeydown="if(event.key === 'Enter') loadAdminUsers(1)">
|
||||
<button onclick="loadAdminUsers(1)" class="admin-search-btn">Tìm kiếm</button>
|
||||
</div>
|
||||
<div id="admin-users-list" class="dashboard-list"></div>
|
||||
<div id="admin-users-list"></div>
|
||||
<div id="admin-users-pagination" class="pagination-container"></div>
|
||||
</div>
|
||||
<div id="tab-system-settings" class="dashboard-tab-pane admin-only">
|
||||
|
||||
+59
-30
@@ -1986,49 +1986,78 @@ async function loadAdminUsers(page = 1) {
|
||||
const { users, totalPages, currentPage, totalUsers } = data;
|
||||
|
||||
let html = `
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Họ tên</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Quyền hạn</th>
|
||||
<th>Reset Password</th>
|
||||
<th>Thao tác</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<div class="admin-management-controls" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px; width: 100%;">
|
||||
<div class="cleanup-row" style="display: flex; justify-content: flex-end; width: 100%;">
|
||||
<button class="cleanup-btn" onclick="openManualCleanupConfirm()" style="background: #1a1a1a; color: #fff; padding: 6px 14px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer;">
|
||||
<i class="fas fa-broom"></i> Dọn dẹp dữ liệu
|
||||
</button>
|
||||
</div>
|
||||
<div class="admin-search-container" style="display: flex; width: 100%; gap: 8px;">
|
||||
<input type="text" id="admin-user-search-input" placeholder="Tìm kiếm theo tên, email, username..." onkeydown="if(event.key === 'Enter') loadAdminUsers(1)" style="flex-grow: 1; background: #1a1a1a; color: #fff; padding: 8px 12px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; outline: none;">
|
||||
<button class="admin-search-btn" onclick="loadAdminUsers(1)" style="background: #1a1a1a; color: #fff; padding: 8px 20px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; white-space: nowrap;">Tìm kiếm</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-users-box-list" style="width: 100%; display: flex; flex-direction: column; gap: 12px;">
|
||||
|
||||
<div class="admin-table-header" style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 16px; align-items: center; text-align: center; padding: 0 16px; color: #a3a3a3; font-size: 14px; font-weight: 600; margin-bottom: 4px;">
|
||||
<div>Họ tên</div>
|
||||
<div>Username</div>
|
||||
<div>Email</div>
|
||||
<div>Quyền hạn</div>
|
||||
<div>Dung lượng</div>
|
||||
<div>Reset Password</div>
|
||||
<div>Thao tác</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
users.forEach(user => {
|
||||
const isRootAdmin = user.role === 'admin';
|
||||
const isRootAdmin = user.role === 'admin' || user.role === 'Chủ sở hữu';
|
||||
const quotaMB = user.storage?.quota ? Math.floor(user.storage.quota / (1024 * 1024)) : 0;
|
||||
const usedMB = user.storage?.used ? (user.storage.used / (1024 * 1024)).toFixed(1) : 0;
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td><input type="text" id="adm-fn-${user._id}" value="${user.fullName || ''}"></td>
|
||||
<td><strong>${user.username}</strong></td>
|
||||
<td><input type="email" id="adm-em-${user._id}" value="${user.email || ''}"></td>
|
||||
<td>
|
||||
<select id="adm-role-${user._id}" ${isRootAdmin ? 'disabled' : ''}>
|
||||
<div class="admin-user-box" style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 16px; align-items: center; text-align: center; background: #1a1a1a; border: 1px solid #262626; padding: 16px; border-radius: 12px;">
|
||||
<div class="card-field">
|
||||
<input type="text" id="adm-fn-${user._id}" value="${user.fullName || ''}" style="width: 100%; background: #111111; color: #fff; padding: 8px 10px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; outline: none;">
|
||||
</div>
|
||||
|
||||
<div class="card-field" style="font-weight: bold; font-size: 14px; color: #fff;">
|
||||
${user.username}
|
||||
</div>
|
||||
|
||||
<div class="card-field">
|
||||
<input type="email" id="adm-em-${user._id}" value="${user.email || ''}" style="width: 100%; background: #111111; color: #fff; padding: 8px 10px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; outline: none;">
|
||||
</div>
|
||||
|
||||
<div class="card-field">
|
||||
<select id="adm-role-${user._id}" ${isRootAdmin ? 'disabled' : ''} style="width: 100%; background: #111111; color: #fff; padding: 8px 10px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; cursor: ${isRootAdmin ? 'not-allowed' : 'pointer'}; outline: none;">
|
||||
<option value="user" ${user.role === 'user' ? 'selected' : ''}>User</option>
|
||||
<option value="editor" ${user.role === 'editor' ? 'selected' : ''}>Editor</option>
|
||||
<option value="moderator" ${user.role === 'moderator' ? 'selected' : ''}>Moderator</option>
|
||||
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="password" id="adm-pw-${user._id}" placeholder="${isRootAdmin ? 'N/A' : 'Mật khẩu mới'}" ${isRootAdmin ? 'disabled' : ''}></td>
|
||||
<td>
|
||||
<button class="edit-btn-small" onclick="updateUserByAdmin('${user._id}')">Lưu</button>
|
||||
${isRootAdmin ? '' : `<button class="delete-btn-small" onclick="deleteUserByAdmin('${user._id}')">Xóa</button>`}
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
|
||||
<div class="card-field" style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
||||
<input type="number" id="adm-quota-${user._id}" value="${quotaMB}" min="0" style="width: 100%; background: #111111; color: #fff; padding: 8px 10px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; text-align: center; outline: none;">
|
||||
<small style="color: #737373; font-size: 11px;">Đã dùng: ${usedMB} MB</small>
|
||||
</div>
|
||||
|
||||
<div class="card-field">
|
||||
<input type="text" id="adm-pw-${user._id}" placeholder="N/A" disabled style="width: 100%; background: #1a1a1a; color: #525252; padding: 8px 10px; border: 1px solid #262626; border-radius: 6px; font-size: 14px; text-align: center; cursor: not-allowed;">
|
||||
</div>
|
||||
|
||||
<div class="card-field">
|
||||
<button class="edit-btn-small" onclick="updateUserByAdmin('${user._id}')" style="width: 100%; background: #1a1a1a; color: #fff; padding: 8px 12px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s;">Lưu</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</tbody></table>';
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
|
||||
// Render Pagination UI
|
||||
if (paginationContainer && totalPages > 1) {
|
||||
paginationContainer.innerHTML = `
|
||||
<button class="pagination-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="loadAdminUsers(${currentPage - 1})">Trang trước</button>
|
||||
@@ -2040,14 +2069,14 @@ async function loadAdminUsers(page = 1) {
|
||||
container.innerHTML = `<p style="color:red">Lỗi: ${e.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
window.updateUserByAdmin = async function(userId) {
|
||||
const token = localStorage.getItem('jwt');
|
||||
const payload = {
|
||||
fullName: document.getElementById(`adm-fn-${userId}`).value,
|
||||
email: document.getElementById(`adm-em-${userId}`).value,
|
||||
role: document.getElementById(`adm-role-${userId}`).value,
|
||||
password: document.getElementById(`adm-pw-${userId}`).value
|
||||
password: document.getElementById(`adm-pw-${userId}`).value,
|
||||
quota: document.getElementById(`adm-quota-${userId}`).value
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user