diff --git a/i18n.py b/i18n.py index c3b91c3..416825f 100644 --- a/i18n.py +++ b/i18n.py @@ -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"}, diff --git a/ui/monitoring_tab.py b/ui/monitoring_tab.py index 3f716f2..48529a0 100644 --- a/ui/monitoring_tab.py +++ b/ui/monitoring_tab.py @@ -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