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();

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Knowledge Hub | Enterprise RAG</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="/static/style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
@@ -48,6 +48,7 @@
<nav class="side-nav">
<div class="nav-item active"><i class="fas fa-comments"></i> <span>Hỏi đáp RAG</span></div>
<button class="sync-btn" id="sync-btn"><i class="fas fa-sync-alt"></i> Đồng bộ SharePoint</button>
<button class="sync-btn" id="history-btn"><i class="fas fa-history"></i> Lịch sử đồng bộ</button>
</nav>
<div class="sync-status" id="sync-status" style="display: none;">
@@ -112,8 +113,19 @@
<!-- Nguồn sẽ được render ở đây -->
</div>
</section>
<!-- Sync History Panel -->
<section class="source-panel" id="history-panel">
<div class="panel-header">
<h3><i class="fas fa-history"></i> Lịch sử đồng bộ</h3>
<button id="close-history"><i class="fas fa-times"></i></button>
</div>
<div class="history-list" id="history-list">
<!-- Lịch sử sẽ được render ở đây -->
</div>
</section>
</div>
<script src="app.js"></script>
<script src="/static/app.js"></script>
</body>
</html>

View File

@@ -344,6 +344,122 @@ textarea {
.source-item h4 { font-size: 14px; margin-bottom: 5px; color: var(--primary); }
.source-item p { font-size: 12px; color: var(--text-muted); line-height: 1.4; }
/* History Panel */
.history-item {
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--glass-border);
border-radius: 12px;
padding: 15px;
margin-bottom: 12px;
cursor: pointer;
transition: 0.3s;
}
.history-item:hover {
background: rgba(255, 255, 255, 0.08);
}
.history-item .history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.history-item .history-time {
font-size: 12px;
color: var(--text-muted);
}
.history-item .history-status {
font-size: 11px;
padding: 2px 8px;
border-radius: 6px;
font-weight: 600;
}
.history-item .history-status.completed {
background: rgba(16, 185, 129, 0.2);
color: #10b981;
}
.history-item .history-status.failed {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
.history-item .history-status.running {
background: rgba(139, 92, 246, 0.2);
color: #8b5cf6;
}
.history-item .history-summary {
display: flex;
gap: 12px;
font-size: 12px;
color: var(--text-muted);
}
.history-item .history-summary span {
display: flex;
align-items: center;
gap: 4px;
}
.history-files {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--glass-border);
display: none;
}
.history-item.expanded .history-files {
display: block;
}
.history-file {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
font-size: 12px;
border-bottom: 1px solid rgba(255,255,255,0.03);
}
.history-file:last-child {
border-bottom: none;
}
.history-file-name {
color: var(--text-main);
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.history-file-status {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
margin-left: 8px;
}
.history-file-status.indexed {
background: rgba(16, 185, 129, 0.2);
color: #10b981;
}
.history-file-status.skipped {
background: rgba(234, 179, 8, 0.2);
color: #eab308;
}
.history-file-status.error {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
/* Login Screen */
.login-screen {
position: fixed;