CI / test (push) Canceled after 0s
## 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>
143 lines
6.5 KiB
Python
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()
|