Files
cowork-local/presentation/monitoring/tabs/sandbox_tab.py
T
f9f6bc01fd
CI / test (push) Canceled after 0s
Feature/delta team/epic r04 (#7)
## Summary

epic r04 - begin refactor

## Change Type

- [x] Cowork feature
- [ ] Bug fix
- [ ] Core AI contribution
- [ ] Test / hardening
- [ ] Performance
- [ ] Documentation

## Related Work

Cowork Task:

Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets

Core AI Issue:

Core Task:

Related PR:

## Scope

What is intentionally included?

What is intentionally NOT included?

## Validation

- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual verification
- [ ] Regression check

Commands / evidence:

## Security Impact

Permission / credential / network / customer data impact:

## Compatibility

- [ ] No breaking change
- [ ] Breaking change documented

## Reviewer Notes

Anything Cowork reviewers should pay attention to.

---------

Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com>
Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com>
Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com>
Co-authored-by: Vu Dam Tuan <vudt15@fpt.com>
Co-authored-by: Hiep Ha Van <hiephv3@fpt.com>
Co-authored-by: Lam Hoang Van <lamhv7@fpt.com>
Reviewed-on: #7
Co-authored-by: Duy Le Huu <duylh19@fpt.com>
2026-08-31 05:15:13 +00:00

143 lines
6.5 KiB
Python

"""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):
"""Thẻ "Trạng thái sandbox": backend cách ly đang dùng, giới hạn tài nguyên và
trạng thái chặn mạng.
"""
def __init__(self, ctx, on_settings_changed: Callable[[], None]):
"""Thẻ thông tin sandbox: cơ chế cách ly đang dùng và các giới hạn của nó."""
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:
"""Mở hộp thoại Cài đặt tới nhóm an toàn, đóng xong thì làm mới thẻ."""
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:
"""Áp lại chữ theo ngôn ngữ đang chọn."""
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:
"""Đọc lại cấu hình an toàn và cập nhật các dòng trạng thái."""
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()