Phase 8 Complete: Sync Audit Log + Frontend Integration

- Thêm audit/sync_audit.py: Ghi lịch sử sync vào audit/sync_log.json
- Thêm API endpoints: /sync/history, /sync/history/{run_id}
- Frontend: Nút 'Lịch sử đồng bộ' + panel expand chi tiết từng file
- Sửa frontend served từ backend (http://localhost:8000)
- Cập nhật DEPLOYMENT_GUIDE với hướng dẫn chạy localhost
- Cập nhật ARCHITECTURE_MAP: Phase 8 hoàn thành
This commit is contained in:
2026-05-11 08:49:10 +00:00
parent f937d1a98e
commit 78372d18ee
8 changed files with 577 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
// API Base URL
const API_BASE = 'http://localhost:8000';
// API Base URL (relative khi served từ backend)
const API_BASE = window.location.origin;
// DOM Elements
const loginScreen = document.getElementById('login-screen');
@@ -19,6 +19,10 @@ const logoutBtn = document.getElementById('logout-btn');
const syncBtn = document.getElementById('sync-btn');
const syncStatus = document.getElementById('sync-status');
const ssoBtn = document.getElementById('sso-btn');
const historyBtn = document.getElementById('history-btn');
const historyPanel = document.getElementById('history-panel');
const historyList = document.getElementById('history-list');
const closeHistory = document.getElementById('close-history');
let chatHistory = [];
let currentUser = null;
@@ -287,5 +291,71 @@ async function pollSyncStatus() {
syncBtn.onclick = triggerSync;
// ====== SYNC HISTORY ======
async function loadSyncHistory() {
try {
const response = await fetch(`${API_BASE}/sync/history?limit=20`);
const data = await response.json();
historyList.innerHTML = '';
if (!data.runs || data.runs.length === 0) {
historyList.innerHTML = '<p style="color: var(--text-muted); font-size: 13px; text-align: center; padding: 20px;">Chưa có lần đồng bộ nào.</p>';
return;
}
data.runs.forEach(run => {
const item = document.createElement('div');
item.className = 'history-item';
const time = new Date(run.started_at).toLocaleString('vi-VN');
const statusClass = run.status;
const statusText = run.status === 'completed' ? 'Hoàn thành' :
run.status === 'failed' ? 'Thất bại' : 'Đang chạy';
item.innerHTML = `
<div class="history-header">
<span class="history-time">${time}</span>
<span class="history-status ${statusClass}">${statusText}</span>
</div>
<div class="history-summary">
<span><i class="fas fa-check-circle" style="color: #10b981"></i> ${run.summary.processed} nạp</span>
<span><i class="fas fa-forward" style="color: #eab308"></i> ${run.summary.skipped} bỏ</span>
<span><i class="fas fa-exclamation-circle" style="color: #ef4444"></i> ${run.summary.errors} lỗi</span>
</div>
<div class="history-files">
${run.files.map(f => `
<div class="history-file">
<span class="history-file-name" title="${f.name}">${f.name}</span>
<span class="history-file-status ${f.status}">${f.chunks > 0 ? f.chunks + ' chunks' : f.status}</span>
</div>
`).join('')}
${run.errors.length > 0 ? `
<div style="margin-top: 8px; font-size: 11px; color: #ef4444;">
${run.errors.map(e => `<div>⚠ ${e}</div>`).join('')}
</div>
` : ''}
</div>
`;
item.onclick = () => item.classList.toggle('expanded');
historyList.appendChild(item);
});
} catch (error) {
console.error('Load history error:', error);
historyList.innerHTML = '<p style="color: #ef4444; font-size: 13px; text-align: center;">Lỗi tải lịch sử.</p>';
}
}
historyBtn.onclick = () => {
historyPanel.classList.toggle('active');
if (historyPanel.classList.contains('active')) {
loadSyncHistory();
}
};
closeHistory.onclick = () => historyPanel.classList.remove('active');
// Init
checkLogin();