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

144
audit/sync_audit.py Normal file
View File

@@ -0,0 +1,144 @@
import json
import os
import logging
from datetime import datetime
from typing import Dict, List, Optional
logger = logging.getLogger("SyncAudit")
AUDIT_DIR = os.path.dirname(os.path.abspath(__file__))
AUDIT_FILE = os.path.join(AUDIT_DIR, "sync_log.json")
def _load_log() -> Dict:
"""Load audit log từ file."""
if os.path.exists(AUDIT_FILE):
try:
with open(AUDIT_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError:
return {"runs": []}
return {"runs": []}
def _save_log(data: Dict):
"""Lưu audit log ra file."""
with open(AUDIT_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def start_run() -> str:
"""
Bắt đầu một sync run mới.
Returns: run_id
"""
run_id = datetime.now().strftime("%Y%m%d_%H%M%S")
data = _load_log()
run = {
"run_id": run_id,
"started_at": datetime.now().isoformat(),
"finished_at": None,
"status": "running",
"summary": {"processed": 0, "skipped": 0, "errors": 0},
"files": [],
"errors": []
}
data["runs"].insert(0, run) # Mới nhất lên đầu
# Giới hạn 50 runs gần nhất
if len(data["runs"]) > 50:
data["runs"] = data["runs"][:50]
_save_log(data)
logger.info(f"Audit: Started run {run_id}")
return run_id
def log_file(run_id: str, name: str, item_id: str, policy: str,
doc_type: str, chunks: int, status: str, detail: str = ""):
"""
Ghi log cho 1 file đã xử lý.
Args:
run_id: ID của sync run
name: Tên file
item_id: SharePoint item ID
policy: Processing policy (requires_ocr, skip_ocr, metadata_only, unsupported)
doc_type: Document type (textual_document, spreadsheet, drawing, binary)
chunks: Số chunks đã tạo
status: indexed, skipped, error
detail: Ghi chú thêm
"""
data = _load_log()
for run in data["runs"]:
if run["run_id"] == run_id:
file_entry = {
"name": name,
"item_id": item_id,
"doc_type": doc_type,
"policy": policy,
"chunks": chunks,
"status": status,
"detail": detail,
"timestamp": datetime.now().isoformat()
}
run["files"].append(file_entry)
# Cập nhật summary
if status == "indexed":
run["summary"]["processed"] += 1
elif status == "skipped":
run["summary"]["skipped"] += 1
elif status == "error":
run["summary"]["errors"] += 1
run["errors"].append(f"{name}: {detail}")
break
_save_log(data)
def finish_run(run_id: str, status: str = "completed"):
"""
Đánh dấu sync run hoàn thành.
"""
data = _load_log()
for run in data["runs"]:
if run["run_id"] == run_id:
run["finished_at"] = datetime.now().isoformat()
run["status"] = status
break
_save_log(data)
logger.info(f"Audit: Finished run {run_id} ({status})")
def get_history(limit: int = 10) -> List[Dict]:
"""
Lấy lịch sử sync runs.
"""
data = _load_log()
return data["runs"][:limit]
def get_run_detail(run_id: str) -> Optional[Dict]:
"""
Lấy chi tiết của 1 sync run.
"""
data = _load_log()
for run in data["runs"]:
if run["run_id"] == run_id:
return run
return None
def get_latest_run() -> Optional[Dict]:
"""
Lấy sync run gần nhất.
"""
data = _load_log()
return data["runs"][0] if data["runs"] else None

128
audit/sync_log.json Normal file
View File

@@ -0,0 +1,128 @@
{
"runs": [
{
"run_id": "20260511_035430",
"started_at": "2026-05-11T03:54:30.817636",
"finished_at": "2026-05-11T03:55:11.377114",
"status": "completed",
"summary": {
"processed": 8,
"skipped": 3,
"errors": 0
},
"files": [
{
"name": "Quy-trinh-nghiem-thu-thiet-bi-v1.pdf",
"item_id": "01BP532D2O74Z6FYQPOVBKE5DBMYDWCWCK",
"doc_type": "textual_document",
"policy": "skip_ocr",
"chunks": 1,
"status": "indexed",
"detail": "",
"timestamp": "2026-05-11T03:54:47.793432"
},
{
"name": "Bien-ban-ban-giao-scan.pdf",
"item_id": "01BP532D7QZ5QES5H675D2Q62S3YNCN7MV",
"doc_type": "textual_document",
"policy": "requires_ocr",
"chunks": 1,
"status": "indexed",
"detail": "",
"timestamp": "2026-05-11T03:54:54.449272"
},
{
"name": "So-do-mat-bang-kho-A1.pdf",
"item_id": "01BP532D5ETRRCEFMAXVF3T5UOAN7ZMWSW",
"doc_type": "textual_document",
"policy": "skip_ocr",
"chunks": 1,
"status": "indexed",
"detail": "",
"timestamp": "2026-05-11T03:54:57.820068"
},
{
"name": "Quy-dinh-luu-tru-ho-so.docx",
"item_id": "01BP532D7R5UFAJ3N3FZC2LUGZ4EWKXVSV",
"doc_type": "textual_document",
"policy": "skip_ocr",
"chunks": 1,
"status": "indexed",
"detail": "",
"timestamp": "2026-05-11T03:55:00.316683"
},
{
"name": "Mo-ta-nghiep-vu.xlsx",
"item_id": "01BP532D2GHD2SMYQZZRBJ5YBYXH32YLCC",
"doc_type": "spreadsheet",
"policy": "skip_ocr",
"chunks": 1,
"status": "indexed",
"detail": "",
"timestamp": "2026-05-11T03:55:02.318406"
},
{
"name": "sample-layout.dxf",
"item_id": "01BP532D2ICBWDIKS4ARA2QXJLUYYPARDV",
"doc_type": "drawing",
"policy": "metadata_only",
"chunks": 0,
"status": "skipped",
"detail": "Native CAD drawing format",
"timestamp": "2026-05-11T03:55:03.301004"
},
{
"name": "unsupported-binary.bin",
"item_id": "01BP532DZ77VD5TK6SVRC3EBEF3PTWPQ7J",
"doc_type": "binary",
"policy": "unsupported",
"chunks": 0,
"status": "skipped",
"detail": "Unsupported or binary extension: .bin",
"timestamp": "2026-05-11T03:55:04.189531"
},
{
"name": "mystery-file.xyzbin",
"item_id": "01BP532D7RTGNA3Q2TRRAIOS7LPEIBRAVT",
"doc_type": "binary",
"policy": "unsupported",
"chunks": 0,
"status": "skipped",
"detail": "Unsupported or binary extension: .xyzbin",
"timestamp": "2026-05-11T03:55:05.013405"
},
{
"name": "README.txt",
"item_id": "01BP532D6PKB3WWG4X5VGYBMG7IFYZTG6M",
"doc_type": "textual_document",
"policy": "skip_ocr",
"chunks": 1,
"status": "indexed",
"detail": "",
"timestamp": "2026-05-11T03:55:06.986536"
},
{
"name": "Bien-ban-noi-bo-restricted.docx",
"item_id": "01BP532D65XW2KEXQQMRF3JVLAQZOKIW2V",
"doc_type": "textual_document",
"policy": "skip_ocr",
"chunks": 1,
"status": "indexed",
"detail": "",
"timestamp": "2026-05-11T03:55:09.096708"
},
{
"name": "Bang-ngan-sach-du-an.xlsx",
"item_id": "01BP532D6KRPO27NSMUBC3FXJYN7TKDMVR",
"doc_type": "spreadsheet",
"policy": "skip_ocr",
"chunks": 1,
"status": "indexed",
"detail": "",
"timestamp": "2026-05-11T03:55:11.376444"
}
],
"errors": []
}
]
}