- ui/monitoring_tab.py (1546 dong) tach thanh presentation/monitoring/** (container + 7 tab/card + shared helper), ui/monitoring_tab.py con lai re-export shim de app.py khong doi. - infrastructure/telemetry/audit_logger.py: CanonicalAuditLogger, core/audit_log.py thanh wrapper mong, tuong thich nguoc 100% voi schema .jsonl cu. - application/monitoring/monitoring_query_service.py: MonitoringQueryService read-only, filter/sort/pagination, khong import PySide6. - Go circular import model_pricing<->usage_tracker va agent_security<-> agent_security_alert (core/agent_security_types.py moi). - infrastructure/sandbox/sandbox_capabilities.py: SandboxCapabilityMatrix theo OS (Windows/Linux/macOS), chua dau noi vao core/sandbox_manager.py. - conftest.py: sua loi checkout khong ten cowork_local khien pytest import nham thu muc khac. - 77 test moi, 167/167 pass. QA da xac nhan UI/business logic khong doi (xem evidence/report/unified_report.html). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
"""Read-only query service over audit events — filter + sort + pagination.
|
|
|
|
Pure Python: no PySide6 import, no UI code. Depends only on an injected
|
|
``AuditEventRepository`` (see ``repository/audit_event_repository.py``), so it
|
|
is fully unit-testable with ``InMemoryAuditEventRepository`` and independent
|
|
of file I/O or Qt.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import List, Optional
|
|
|
|
from .dto.audit_event_dto import AuditEventDTO
|
|
from .repository.audit_event_repository import AuditEventRepository
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Page:
|
|
items: List[AuditEventDTO]
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
|
|
@property
|
|
def has_more(self) -> bool:
|
|
return self.page * self.page_size < self.total
|
|
|
|
|
|
class MonitoringQueryService:
|
|
"""Read-only. Callers ask for a filtered/sorted/paginated slice of the
|
|
audit log; this service never writes anything."""
|
|
|
|
def __init__(self, repository: AuditEventRepository) -> None:
|
|
self._repository = repository
|
|
|
|
def query(self, kind: Optional[str] = None, ok: Optional[bool] = None,
|
|
text: Optional[str] = None, sort_by: str = "ts",
|
|
descending: bool = True, page: int = 1, page_size: int = 50) -> Page:
|
|
events = self._repository.load(kind=kind)
|
|
|
|
if ok is not None:
|
|
events = [e for e in events if e.ok == ok]
|
|
if text:
|
|
needle = text.lower()
|
|
events = [e for e in events
|
|
if needle in e.name.lower() or needle in e.detail.lower()]
|
|
|
|
events = sorted(events, key=lambda e: getattr(e, sort_by, ""), reverse=descending)
|
|
|
|
total = len(events)
|
|
page = max(1, page)
|
|
start = (page - 1) * page_size
|
|
items = events[start:start + page_size] if page_size > 0 else events
|
|
return Page(items=items, total=total, page=page, page_size=page_size)
|