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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
86c27e2e79
commit
40b12ecb15
@@ -0,0 +1,135 @@
|
||||
"""Sandbox Details card — the current sandbox id/status/uptime/resource
|
||||
limits/network state, with a collapsible fold that ALSO nests the
|
||||
Permissions card inside it (see ``security_settings_tab.PermissionsCard``),
|
||||
exactly matching the pre-refactor ``ui/monitoring_tab.py`` layout: Sandbox
|
||||
and Permissions answer the same question ("what is the agent allowed to
|
||||
touch?"), so they share one fold rather than being two independent
|
||||
top-level sections. This card is embedded inside ``overview_tab.OverviewTab``
|
||||
at the same position the original ``QGroupBox`` occupied — no new top-level
|
||||
tab is added, so the visible UI is unchanged.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Callable
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
from ....i18n import tr
|
||||
from ..shared.badges import apply_badge
|
||||
from ..shared.layout_helpers import kv_row
|
||||
from ..shared.open_settings import open_settings_and_notify
|
||||
from .security_settings_tab import PermissionsCard
|
||||
|
||||
|
||||
class SandboxDetailsCard(QGroupBox):
|
||||
def __init__(self, ctx, on_settings_changed: Callable[[], None]):
|
||||
super().__init__()
|
||||
self._ctx = ctx
|
||||
self._on_settings_changed = on_settings_changed
|
||||
self.setObjectName("monSection")
|
||||
sbx_lay = QVBoxLayout(self)
|
||||
|
||||
self.summary_lbl = QLabel()
|
||||
self.summary_lbl.setWordWrap(True)
|
||||
sbx_lay.addWidget(self.summary_lbl)
|
||||
|
||||
self.more_btn = QPushButton()
|
||||
self.more_btn.setObjectName("co4eSectionAction")
|
||||
self.more_btn.setFlat(True)
|
||||
self.more_btn.setCheckable(True)
|
||||
self.more_btn.setCursor(Qt.PointingHandCursor)
|
||||
sbx_lay.addWidget(self.more_btn, 0, Qt.AlignLeft)
|
||||
|
||||
self._detail = QWidget()
|
||||
self._detail.setVisible(False)
|
||||
self.more_btn.toggled.connect(self._detail.setVisible)
|
||||
self.more_btn.toggled.connect(self._sync_more_label)
|
||||
sbx_lay.addWidget(self._detail)
|
||||
detail_lay = QVBoxLayout(self._detail)
|
||||
detail_lay.setContentsMargins(0, 4, 0, 0)
|
||||
|
||||
self.id_lbl, self.id_val = kv_row(detail_lay)
|
||||
self.status_lbl, self.status_val = kv_row(detail_lay)
|
||||
self.status_val.setObjectName("badgeSuccess")
|
||||
self.created_lbl, self.created_val = kv_row(detail_lay)
|
||||
self.uptime_lbl, self.uptime_val = kv_row(detail_lay)
|
||||
|
||||
limits_row = QHBoxLayout()
|
||||
self.limits_lbl = QLabel()
|
||||
self.limits_lbl.setObjectName("hint")
|
||||
self.limits_lbl.setWordWrap(True)
|
||||
self.edit_btn = QPushButton()
|
||||
self.edit_btn.setFlat(True)
|
||||
self.edit_btn.clicked.connect(self._open_settings)
|
||||
limits_row.addWidget(self.limits_lbl, 1)
|
||||
limits_row.addWidget(self.edit_btn)
|
||||
detail_lay.addLayout(limits_row)
|
||||
|
||||
self.net_lbl, self.net_val = kv_row(detail_lay)
|
||||
|
||||
# The Permissions card is nested inside THIS fold, not a sibling
|
||||
# section — matches the original layout exactly.
|
||||
self.permissions_card = PermissionsCard(ctx, on_settings_changed)
|
||||
detail_lay.addWidget(self.permissions_card)
|
||||
|
||||
def _open_settings(self) -> None:
|
||||
open_settings_and_notify(self._ctx, self, self._on_settings_changed)
|
||||
|
||||
def _sync_more_label(self, *_a) -> None:
|
||||
"""Label the fold with what it will do next."""
|
||||
open_ = self.more_btn.isChecked()
|
||||
self.more_btn.setText(("▾ " if open_ else "▸ ") + tr("monitoring.overview_sbx_detail"))
|
||||
|
||||
def retranslate(self) -> None:
|
||||
self.setTitle(tr("monitoring.overview_sandbox_details_title").upper().replace("&", "&&"))
|
||||
self.id_lbl.setText(tr("monitoring.overview_sandbox_id"))
|
||||
self.status_lbl.setText(tr("monitoring.overview_status"))
|
||||
self.created_lbl.setText(tr("monitoring.overview_created"))
|
||||
self.uptime_lbl.setText(tr("monitoring.overview_uptime"))
|
||||
self.edit_btn.setText(tr("monitoring.overview_edit"))
|
||||
self.net_lbl.setText(tr("monitoring.overview_network_label"))
|
||||
self.permissions_card.retranslate()
|
||||
self._sync_more_label()
|
||||
|
||||
def refresh(self) -> None:
|
||||
sec = self._ctx.config.agent_security
|
||||
net_blocked = bool(sec.get("block_network"))
|
||||
|
||||
self.id_val.setText(f"sbx_{os.getpid():x}")
|
||||
self.status_val.setText(tr("monitoring.overview_status_running"))
|
||||
self.created_val.setText(datetime.fromtimestamp(self._ctx.started_at).strftime("%H:%M:%S"))
|
||||
uptime_s = max(0, int(time.time() - self._ctx.started_at))
|
||||
h, rem = divmod(uptime_s, 3600)
|
||||
m, s = divmod(rem, 60)
|
||||
self.uptime_val.setText(f"{h}h {m}m {s}s" if h else f"{m}m {s}s")
|
||||
|
||||
limit_parts = []
|
||||
if sec.get("resource_limit_cpu_percent"):
|
||||
limit_parts.append(f"CPU {sec['resource_limit_cpu_percent']}%")
|
||||
if sec.get("resource_limit_memory_mb"):
|
||||
limit_parts.append(f"MEM {sec['resource_limit_memory_mb']}MB")
|
||||
if sec.get("resource_limit_disk_mb"):
|
||||
limit_parts.append(f"DISK {sec['resource_limit_disk_mb']}MB")
|
||||
limits_text = ", ".join(limit_parts) if limit_parts else tr("monitoring.na")
|
||||
self.limits_lbl.setText(tr("monitoring.overview_resource_limits") + ": " + limits_text)
|
||||
|
||||
self.net_val.setText(
|
||||
tr("monitoring.overview_network_disabled") if net_blocked
|
||||
else tr("monitoring.overview_network_enabled"))
|
||||
apply_badge(self.net_val, "badgeWarn" if net_blocked else "badgeSuccess")
|
||||
|
||||
# The one line the wireframe shows; the detail above stays a fold away.
|
||||
self.summary_lbl.setText(" · ".join([
|
||||
f'{tr("monitoring.overview_perm_fs")}: {tr("monitoring.overview_perm_fs_value")}',
|
||||
f'{tr("monitoring.overview_perm_network")}: '
|
||||
f'{tr("monitoring.overview_network_disabled") if net_blocked else tr("monitoring.overview_network_enabled")}',
|
||||
f'{tr("monitoring.overview_perm_process")}: {tr("monitoring.overview_perm_process_value")}',
|
||||
f'{tr("monitoring.overview_resource_limits")}: {limits_text}',
|
||||
]))
|
||||
self._sync_more_label()
|
||||
|
||||
self.permissions_card.refresh()
|
||||
Reference in New Issue
Block a user