"""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()