Cập nhật tính năng quản lí dữ liệu mồ côi

This commit is contained in:
2026-06-09 15:32:06 +07:00
parent 7f32eb816c
commit 13e1c6049d
3 changed files with 254 additions and 1 deletions
+23
View File
@@ -138,6 +138,15 @@
</div>
<div id="tab-user-management" class="dashboard-tab-pane admin-only">
<h3>Quản lí người dùng</h3>
<div class="admin-maintenance-header" style="margin-bottom: 20px;">
<button class="edit-btn-large" onclick="openManualCleanupConfirm()" style="background: #17a2b8; width: auto; padding: 10px 20px; font-size: 14px;">
🧹 Dọn dẹp dữ liệu mồ côi
</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>
@@ -423,6 +432,20 @@
</div>
</div>
<!-- Maintenance Confirmation Modal -->
<div id="maintenance-confirm-modal" class="modal-overlay">
<div class="modal-content action-modal-content logout-modal-dark">
<div id="maintenance-confirm-icon" style="font-size: 40px; color: #ffc107; margin-bottom: 10px;">⚙️</div>
<h2 style="color: #fff; margin-bottom: 10px;" id="maintenance-confirm-title">Xác nhận bảo trì</h2>
<p style="color: #ccc; margin-bottom: 25px;" id="maintenance-confirm-desc">Nội dung xác nhận...</p>
<input type="hidden" id="maintenance-action-type">
<div class="action-buttons">
<button id="maintenance-confirm-btn" class="edit-btn-large" style="background: #28a745;">Xác nhận thực hiện</button>
<button onclick="closeMaintenanceConfirm()" class="edit-btn-large" style="background: #6c757d;">Hủy bỏ</button>
</div>
</div>
</div>
<!-- Logout Confirmation Modal -->
<div id="logout-confirm-modal" class="modal-overlay">
<div class="modal-content action-modal-content logout-modal-dark">
+106 -1
View File
@@ -1666,11 +1666,87 @@ window.deleteUserByAdmin = async function(userId) {
});
const data = await res.json();
if (!res.ok) throw new Error(data.message);
showNotification(data.message, 'success');
// Hiển thị thông báo thành công kèm báo cáo dọn dẹp nếu có
let successMsg = data.message;
if (data.report) {
successMsg += `\n(Đã dọn dẹp: ${data.report.scenesDeleted} Scenes, ${data.report.filesRemoved} Files)`;
}
showNotification(successMsg, 'success');
loadAdminUsers();
} catch (e) { showNotification(e.message, 'error'); }
};
/**
* Giai đoạn 2 & 3: Các hàm xử lý bảo trì cho Admin tối cao
*/
window.openManualCleanupConfirm = function() {
const modal = document.getElementById('maintenance-confirm-modal');
document.getElementById('maintenance-confirm-title').innerText = "Dọn dẹp hệ thống";
document.getElementById('maintenance-confirm-desc').innerText = "Hệ thống sẽ quét và xóa bỏ tất cả các Scene, Hotspot và Asset không còn liên kết hợp lệ. Thao tác này không thể hoàn tác.";
document.getElementById('maintenance-action-type').value = 'cleanup';
document.getElementById('maintenance-confirm-btn').onclick = runManualCleanup;
modal.style.display = 'flex';
};
window.closeMaintenanceConfirm = () => {
document.getElementById('maintenance-confirm-modal').style.display = 'none';
};
async function runManualCleanup() {
const token = localStorage.getItem('jwt');
closeMaintenanceConfirm();
try {
const res = await fetch(`${API_BASE_URL}/admin/maintenance/cleanup`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await res.json();
if (!res.ok) throw new Error(data.message);
const report = data.report;
showSuccessModal(`Dọn dẹp hoàn tất!\n- Scenes xóa: ${report.scenesDeleted}\n- Files xóa: ${report.filesRemoved}`);
} catch (e) { showNotification(e.message, 'error'); }
}
window.checkStrayFiles = async function() {
const token = localStorage.getItem('jwt');
try {
const res = await fetch(`${API_BASE_URL}/admin/maintenance/stray-files`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await res.json();
if (!res.ok) throw new Error(data.message);
if (data.count === 0) {
showSuccessModal("Tuyệt vời! Không tìm thấy tệp tin rác nào trong hệ thống.", '✨');
} else {
const modal = document.getElementById('maintenance-confirm-modal');
document.getElementById('maintenance-confirm-title').innerText = "Phát hiện tệp tin rác";
document.getElementById('maintenance-confirm-desc').innerText = `Tìm thấy ${data.count} file trong thư mục uploads không có bản ghi trong Database. Bạn có muốn xóa chúng để tiết kiệm dung lượng?`;
document.getElementById('maintenance-action-type').value = 'stray';
document.getElementById('maintenance-confirm-btn').onclick = deleteStrayFiles;
modal.style.display = 'flex';
}
} catch (e) { showNotification(e.message, 'error'); }
};
async function deleteStrayFiles() {
const token = localStorage.getItem('jwt');
closeMaintenanceConfirm();
try {
const res = await fetch(`${API_BASE_URL}/admin/maintenance/cleanup?deleteStray=true`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await res.json();
if (!res.ok) throw new Error(data.message);
showSuccessModal(`Đã dọn dẹp sạch sẽ ${data.report.filesRemoved} tệp tin rác khỏi máy chủ.`);
loadMediaStats(); // Cập nhật lại dung lượng hiển thị
} catch (e) { showNotification(e.message, 'error'); }
}
/**
* Tải và hiển thị kho ảnh/media của người dùng
*/
@@ -2171,6 +2247,35 @@ async function handleRestore(input) {
}
}
/**
* Cập nhật cấu hình hệ thống
*/
async function updateSystemSettings(e) {
e.preventDefault();
const token = localStorage.getItem('jwt');
const timezone = document.getElementById('sys-timezone').value;
const language = document.getElementById('sys-language').value;
try {
const res = await fetch(`${API_BASE_URL}/system/settings`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ timezone, language })
});
if (res.ok) {
showNotification("Cấu hình hệ thống đã được lưu!", "success");
// Tải lại cài đặt để áp dụng ngôn ngữ mới ngay lập tức
fetchSystemSettings();
}
} catch (err) {
showNotification("Lỗi lưu cấu hình: " + err.message, "error");
}
}
/**
* Opens a specific tab within the dashboard.
* @param {string} tabName - The ID of the tab pane to open (e.g., 'profile', 'my-scenes').