fix(monitoring): Overview's middle sections are one line each, as drawn

The KPI row already matched the wireframe, but the two sections under it did
not, which is what still read as "unchanged":

  * TÀI NGUYÊN was four stretched columns — on a 1900px screen each value sat
    at the far right of its quarter, with a 700px progress bar drawn as a rule
    between. It is now one line: CPU 0% · Bộ nhớ 141 MB · Disk I/O 1 MB/s ·
    Mạng 1 KB/s, packed left. 130px → 57px. The bars are kept and still
    updated, just not drawn.

  * SANDBOX and QUYỀN were two four-row columns side by side. The wireframe
    draws ONE section with one line, so they are merged: the summary line
    carries file system · network · process · resource limits, and both the
    sandbox rows (ID, created, uptime) and the permission rows sit behind a
    "Chi tiết" fold. Nothing is dropped — 168px → 80px closed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
NamPDT
2026-08-17 16:07:44 +09:00
co-authored by Claude Opus 5
parent 340fbaf56f
commit 175be15c4b
2 changed files with 67 additions and 28 deletions
+4
View File
@@ -2388,6 +2388,10 @@ STRINGS: Dict[str, Dict[str, str]] = {
"monitoring.col_action": {"en": "Action", "ja": "アクション", "vi": "Hành động"},
# The fourth KPI tile on Overview, as the wireframe labels it.
"monitoring.overview_calls": {"en": "Calls", "ja": "呼び出し", "vi": "Lượt gọi"},
# The fold under the Sandbox summary line — the wireframe shows only the
# summary, so the ID / created / uptime / limits rows live behind this.
"monitoring.overview_sbx_detail": {
"en": "Details", "ja": "詳細", "vi": "Chi tiết"},
"monitoring.col_detail": {"en": "Detail", "ja": "詳細", "vi": "Chi tiết"},
"monitoring.col_account": {"en": "Account", "ja": "アカウント", "vi": "Tài khoản"},
"monitoring.col_machine": {"en": "Machine", "ja": "マシン", "vi": "Máy"},
+63 -28
View File
@@ -497,39 +497,42 @@ class MonitoringTab(QWidget):
# ---- Resource Usage -------------------------------------------------
self.ov_resource_group = QGroupBox()
self.ov_resource_group.setObjectName("monSection")
# ONE compact line, as the wireframe writes it: name and value sit
# together and the pairs are separated by a middle dot, packed left —
# spread across the full width they read as four unrelated columns with
# the value stranded at the far edge of a 1900px screen.
res_lay = QHBoxLayout(self.ov_resource_group)
res_lay.setSpacing(18)
res_lay.setSpacing(6)
self._res_first = True
def _pair():
if not self._res_first:
sep = QLabel("·")
sep.setObjectName("hint")
res_lay.addWidget(sep)
self._res_first = False
lbl = QLabel(); lbl.setObjectName("hint")
val = QLabel()
res_lay.addWidget(lbl)
res_lay.addWidget(val)
return lbl, val
def _bar_row():
# One metric as a compact column: name, value, and the gauge under
# them. The bars stay — the wireframe writes this section as a
# single line, and dropping them would lose the at-a-glance load.
cell = QVBoxLayout()
cell.setSpacing(2)
top = QHBoxLayout()
lbl = QLabel()
val = QLabel(); val.setAlignment(Qt.AlignRight)
top.addWidget(lbl); top.addStretch(1); top.addWidget(val)
bar = QProgressBar(); bar.setFixedHeight(6); bar.setTextVisible(False)
cell.addLayout(top); cell.addWidget(bar)
res_lay.addLayout(cell, 1)
# The gauge is kept and updated, but off the line: at a glance the
# number is what is read, and the bar was drawing a 700px rule.
lbl, val = _pair()
bar = QProgressBar()
bar.setVisible(False)
return lbl, bar, val
def _text_row():
cell = QVBoxLayout()
cell.setSpacing(2)
row = QHBoxLayout()
lbl = QLabel(); val = QLabel(); val.setAlignment(Qt.AlignRight)
row.addWidget(lbl); row.addStretch(1); row.addWidget(val)
cell.addLayout(row)
cell.addSpacing(6)
res_lay.addLayout(cell, 1)
return lbl, val
return _pair()
self.ov_cpu_lbl, self.ov_cpu_bar, self.ov_cpu_val = _bar_row()
self.ov_mem_lbl, self.ov_mem_bar, self.ov_mem_val = _bar_row()
self.ov_disk_lbl, self.ov_disk_val = _text_row()
self.ov_network_lbl, self.ov_network_val = _text_row()
res_lay.addStretch(1)
# ---- Model pricing (beside the CPU/resource group) ------------------
self.ov_pricing_group = QGroupBox()
@@ -578,6 +581,22 @@ class MonitoringTab(QWidget):
self.ov_sandbox_details_group = QGroupBox()
self.ov_sandbox_details_group.setObjectName("monSection")
sbx_lay = QVBoxLayout(self.ov_sandbox_details_group)
self.ov_sbx_summary = QLabel()
self.ov_sbx_summary.setWordWrap(True)
sbx_lay.addWidget(self.ov_sbx_summary)
self.ov_sbx_more_btn = QPushButton()
self.ov_sbx_more_btn.setObjectName("co4eSectionAction")
self.ov_sbx_more_btn.setFlat(True)
self.ov_sbx_more_btn.setCheckable(True)
self.ov_sbx_more_btn.setCursor(Qt.PointingHandCursor)
sbx_lay.addWidget(self.ov_sbx_more_btn, 0, Qt.AlignLeft)
self._sbx_detail = QWidget()
self._sbx_detail.setVisible(False)
self.ov_sbx_more_btn.toggled.connect(self._sbx_detail.setVisible)
self.ov_sbx_more_btn.toggled.connect(self._sync_sbx_more_label)
sbx_lay.addWidget(self._sbx_detail)
sbx_lay = QVBoxLayout(self._sbx_detail)
sbx_lay.setContentsMargins(0, 4, 0, 0)
def _kv():
row = QHBoxLayout()
@@ -607,10 +626,7 @@ class MonitoringTab(QWidget):
self.ov_sbx_net_lbl, self.ov_sbx_net_val = _kv()
# Sandbox and Permissions answer the same question ("what is the agent
# allowed to touch?"), so they share one full-width row.
self._sbx_perm_row = QHBoxLayout()
self._sbx_perm_row.setSpacing(12)
self._sbx_perm_row.addWidget(self.ov_sandbox_details_group, 1)
root.addLayout(self._sbx_perm_row)
root.addWidget(self.ov_sandbox_details_group)
# ---- Permissions -----------------------------------------------
self.ov_permissions_group = QGroupBox()
@@ -632,8 +648,9 @@ class MonitoringTab(QWidget):
self.ov_perm_edit_btn = QPushButton()
self.ov_perm_edit_btn.setFlat(True)
self.ov_perm_edit_btn.clicked.connect(self._open_settings_and_refresh)
perm_lay.addWidget(self.ov_perm_edit_btn, 0, Qt.AlignRight)
self._sbx_perm_row.addWidget(self.ov_permissions_group, 1)
perm_lay.addWidget(self.ov_perm_edit_btn, 0, Qt.AlignLeft)
# Inside the same fold as the sandbox rows — one section, one line.
self._sbx_detail.layout().addWidget(self.ov_permissions_group)
# ---- Model pricing — its own section, full width ------------------
root.addWidget(self.ov_pricing_group)
@@ -898,6 +915,12 @@ class MonitoringTab(QWidget):
ut.format_cost(sum(costs.values()), pricing, digits=2))
self._refresh_budget()
def _sync_sbx_more_label(self, *_a) -> None:
"""Label the fold with what it will do next."""
open_ = self.ov_sbx_more_btn.isChecked()
self.ov_sbx_more_btn.setText(
("▾ " if open_ else "▸ ") + tr("monitoring.overview_sbx_detail"))
def _apply_budget(self) -> None:
"""Persist the spin box's value as the new budget — starts a fresh
remaining-balance window (spend before now is no longer counted)."""
@@ -959,6 +982,18 @@ class MonitoringTab(QWidget):
tr("monitoring.overview_network_disabled") if net_blocked
else tr("monitoring.overview_network_enabled"))
self._set_badge(self.ov_sbx_net_val, "badgeWarn" if net_blocked else "badgeSuccess")
# The one line the wireframe shows; the detail above stays a fold away.
self.ov_sbx_summary.setText(" · ".join([
f'{tr("monitoring.overview_perm_fs")}: '
f'{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")}: '
f'{tr("monitoring.overview_perm_process_value")}',
f'{tr("monitoring.overview_resource_limits")}: '
f'{", ".join(limit_parts) if limit_parts else tr("monitoring.na")}',
]))
self._sync_sbx_more_label()
self.ov_perm_network_val.setText(
tr("monitoring.overview_perm_network_blocked") if net_blocked