Files
poc_system/audit/sync_audit.py
phuongtc 78372d18ee 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
2026-05-11 08:49:10 +00:00

145 lines
3.8 KiB
Python

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