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:
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user