Files
cowork-local/presentation/monitoring/shared/formatters.py
T
Hiep Ha VanandClaude Sonnet 5 40b12ecb15 refactor(monitoring): N2 - tach monitoring_tab.py, CanonicalAuditLogger, MonitoringQueryService, go circular import, sandbox matrix
- 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>
2026-08-25 23:52:36 +09:00

114 lines
3.5 KiB
Python

"""Formatting helpers shared by the Monitoring tabs — timestamps, byte
counts, and the per-agent colour-coded initials avatar.
Extracted from ``ui/monitoring_tab.py`` verbatim (same output for the same
input); the three timestamp formatters used to each repeat their own
``datetime.fromisoformat`` + ``try/except (TypeError, ValueError)`` guard —
that parse step is now a single shared ``_parse_iso`` helper.
"""
from __future__ import annotations
from datetime import datetime
from typing import Optional
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QPixmap
from ....i18n import tr
def _parse_iso(ts: str) -> Optional[datetime]:
try:
return datetime.fromisoformat(ts)
except (TypeError, ValueError):
return None
def fmt_bytes(n: float) -> str:
for unit in ("B", "KB", "MB", "GB"):
if n < 1024:
return f"{n:.0f} {unit}"
n /= 1024
return f"{n:.1f} TB"
def fmt_event_time(ts: str) -> str:
""""dd/MM hh:mm" for the Time column — e.g. 25/05 15:03."""
dt = _parse_iso(ts)
return dt.strftime("%d/%m %H:%M") if dt else ts
_MIDDLE_DOT = chr(0xB7)
def fmt_event_time_full(ts: str) -> str:
""""dd/MM/yyyy [middle dot] HH:mm:ss" — the detail panel's Thoi gian field."""
dt = _parse_iso(ts)
return dt.strftime(f"%d/%m/%Y {_MIDDLE_DOT} %H:%M:%S") if dt else ts
def relative_time(ts: str) -> str:
"""A short "Xm ago"-style string for an audit-log ``ts``; "" if
unparsable."""
dt = _parse_iso(ts)
if dt is None:
return ""
delta = (datetime.now() - dt).total_seconds()
if delta < 60:
return tr("monitoring.time_just_now")
if delta < 3600:
return tr("monitoring.time_minutes_ago", n=int(delta // 60))
if delta < 86400:
return tr("monitoring.time_hours_ago", n=int(delta // 3600))
return tr("monitoring.time_days_ago", n=int(delta // 86400))
def event_id(ts: str, row: int) -> str:
"""A display-only id in the ``evt_<timestamp digits>_<row>`` shape the
mockup uses — the real audit log has no native event id, so this is
derived from the timestamp and the row's position in the currently
displayed (sorted) table, not persisted anywhere."""
digits = "".join(ch for ch in ts if ch.isdigit())[:12]
return f"evt_{digits}_{row:03d}"
def agent_initials(name: str) -> str:
"""First letter of each word, max 2."""
return "".join(w[0] for w in name.split() if w)[:2].upper()
def agent_avatar_colour(name: str) -> str:
"""A fixed identity colour per agent kind, unchanged by theme."""
if "Security" in name:
return "#D13438"
if "Cowork" in name:
return "#0078D4"
if name == "schedule" or "Task" in name:
return "#FFB900"
if name == "graphrag" or "Knowledge" in name:
return "#8764B8"
if "Code" in name:
return "#107C10"
if "Planner" in name or "Reasoning" in name:
return "#8A8886"
return "#0078D4"
def agent_avatar_icon(name: str, size: int = 20) -> QIcon:
"""A small round initials badge for the Agent column."""
pm = QPixmap(size, size)
pm.fill(Qt.transparent)
p = QPainter(pm)
p.setRenderHint(QPainter.Antialiasing)
p.setPen(Qt.NoPen)
p.setBrush(QColor(agent_avatar_colour(name)))
p.drawEllipse(0, 0, size, size)
font = QFont()
font.setPixelSize(max(7, size // 2))
font.setBold(True)
p.setFont(font)
p.setPen(QColor("#FFFFFF"))
p.drawText(pm.rect(), Qt.AlignCenter, agent_initials(name))
p.end()
return QIcon(pm)