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) => {
|
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' });
|
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') return res.status(403).json({ message: 'Forbidden' });
|
||||||
try {
|
try {
|
||||||
const { fullName, email, role, password } = req.body;
|
const { fullName, email, role, password, quota } = req.body;
|
||||||
const user = await User.findById(req.params.id);
|
const user = await User.findById(req.params.id);
|
||||||
if (!user) return res.status(404).json({ message: 'User not found' });
|
if (!user) return res.status(404).json({ message: 'User not found' });
|
||||||
if (fullName) user.fullName = fullName;
|
if (fullName) user.fullName = fullName;
|
||||||
if (email) user.email = email;
|
if (email) user.email = email;
|
||||||
if (role && user.role !== 'admin') user.role = role;
|
if (role && user.role !== 'admin') user.role = role;
|
||||||
if (password) user.password = password;
|
if (password) user.password = password;
|
||||||
|
if (quota !== undefined) {
|
||||||
|
user.storage = { ...user.storage, quota: parseInt(quota) * 1024 * 1024 };
|
||||||
|
}
|
||||||
await user.save();
|
await user.save();
|
||||||
res.json({ message: 'User updated' });
|
res.json({ message: 'User updated' });
|
||||||
} catch (error) { res.status(500).json({ message: error.message }); }
|
} 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(1) { min-width: 160px; } /* Họ tên */
|
||||||
.admin-table th:nth-child(2) { min-width: 120px; } /* Username */
|
.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(3) { min-width: 200px; } /* Email */
|
||||||
.admin-table th:nth-child(4) { min-width: 130px; } /* Quyền hạn */
|
.admin-table th:nth-child(4) { min-width: 120px; } /* Quyền hạn */
|
||||||
.admin-table th:nth-child(5) { min-width: 140px; } /* Reset Password */
|
.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 th:nth-child(6) { min-width: 140px; } /* Thao tác */
|
||||||
|
|
||||||
.admin-table td input, .admin-table td select {
|
.admin-table td input, .admin-table td select {
|
||||||
@@ -1204,28 +1205,127 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Admin User Management Header */
|
/* Admin User Management Header */
|
||||||
.admin-management-header {
|
.admin-management-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
gap: 15px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cleanup-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.cleanup-btn {
|
.cleanup-btn {
|
||||||
background: transparent; /* Nền trùng màu dashboard */
|
background: #444444 !important;
|
||||||
color: #fff;
|
color: #222222 !important;
|
||||||
padding: 6px 12px;
|
padding: 0 20px !important;
|
||||||
font-size: 13px;
|
height: 36px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: none !important;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cleanup-btn:hover {
|
.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 {
|
.admin-search-container {
|
||||||
|
|||||||
+1
-11
@@ -140,17 +140,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tab-user-management" class="dashboard-tab-pane admin-only">
|
<div id="tab-user-management" class="dashboard-tab-pane admin-only">
|
||||||
<div class="admin-management-header">
|
<div id="admin-users-list"></div>
|
||||||
|
|
||||||
<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-pagination" class="pagination-container"></div>
|
<div id="admin-users-pagination" class="pagination-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tab-system-settings" class="dashboard-tab-pane admin-only">
|
<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;
|
const { users, totalPages, currentPage, totalUsers } = data;
|
||||||
|
|
||||||
let html = `
|
let html = `
|
||||||
<table class="admin-table">
|
<div class="admin-management-controls" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px; width: 100%;">
|
||||||
<thead>
|
<div class="cleanup-row" style="display: flex; justify-content: flex-end; width: 100%;">
|
||||||
<tr>
|
<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;">
|
||||||
<th>Họ tên</th>
|
<i class="fas fa-broom"></i> Dọn dẹp dữ liệu
|
||||||
<th>Username</th>
|
</button>
|
||||||
<th>Email</th>
|
</div>
|
||||||
<th>Quyền hạn</th>
|
<div class="admin-search-container" style="display: flex; width: 100%; gap: 8px;">
|
||||||
<th>Reset Password</th>
|
<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;">
|
||||||
<th>Thao tác</th>
|
<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>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
</div>
|
||||||
<tbody>
|
|
||||||
|
<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 => {
|
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 += `
|
html += `
|
||||||
<tr>
|
<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;">
|
||||||
<td><input type="text" id="adm-fn-${user._id}" value="${user.fullName || ''}"></td>
|
<div class="card-field">
|
||||||
<td><strong>${user.username}</strong></td>
|
<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;">
|
||||||
<td><input type="email" id="adm-em-${user._id}" value="${user.email || ''}"></td>
|
</div>
|
||||||
<td>
|
|
||||||
<select id="adm-role-${user._id}" ${isRootAdmin ? 'disabled' : ''}>
|
<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="user" ${user.role === 'user' ? 'selected' : ''}>User</option>
|
||||||
<option value="editor" ${user.role === 'editor' ? 'selected' : ''}>Editor</option>
|
<option value="editor" ${user.role === 'editor' ? 'selected' : ''}>Editor</option>
|
||||||
<option value="moderator" ${user.role === 'moderator' ? 'selected' : ''}>Moderator</option>
|
<option value="moderator" ${user.role === 'moderator' ? 'selected' : ''}>Moderator</option>
|
||||||
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin</option>
|
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
<td><input type="password" id="adm-pw-${user._id}" placeholder="${isRootAdmin ? 'N/A' : 'Mật khẩu mới'}" ${isRootAdmin ? 'disabled' : ''}></td>
|
|
||||||
<td>
|
<div class="card-field" style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
||||||
<button class="edit-btn-small" onclick="updateUserByAdmin('${user._id}')">Lưu</button>
|
<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;">
|
||||||
${isRootAdmin ? '' : `<button class="delete-btn-small" onclick="deleteUserByAdmin('${user._id}')">Xóa</button>`}
|
<small style="color: #737373; font-size: 11px;">Đã dùng: ${usedMB} MB</small>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
|
<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;
|
container.innerHTML = html;
|
||||||
|
|
||||||
// Render Pagination UI
|
|
||||||
if (paginationContainer && totalPages > 1) {
|
if (paginationContainer && totalPages > 1) {
|
||||||
paginationContainer.innerHTML = `
|
paginationContainer.innerHTML = `
|
||||||
<button class="pagination-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="loadAdminUsers(${currentPage - 1})">Trang trước</button>
|
<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>`;
|
container.innerHTML = `<p style="color:red">Lỗi: ${e.message}</p>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.updateUserByAdmin = async function(userId) {
|
window.updateUserByAdmin = async function(userId) {
|
||||||
const token = localStorage.getItem('jwt');
|
const token = localStorage.getItem('jwt');
|
||||||
const payload = {
|
const payload = {
|
||||||
fullName: document.getElementById(`adm-fn-${userId}`).value,
|
fullName: document.getElementById(`adm-fn-${userId}`).value,
|
||||||
email: document.getElementById(`adm-em-${userId}`).value,
|
email: document.getElementById(`adm-em-${userId}`).value,
|
||||||
role: document.getElementById(`adm-role-${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 {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user