traffic: expose runtime appmarks items + show in gui

This commit is contained in:
beckline
2026-02-15 21:09:46 +03:00
parent b040b9e7d7
commit 1a96e849bb
6 changed files with 261 additions and 0 deletions

View File

@@ -393,6 +393,44 @@ RU: Восстанавливает маршруты/nft из последнег
tab_apps_layout.addWidget(run_group)
marks_group = QGroupBox("Active runtime marks (TTL)")
marks_layout = QVBoxLayout(marks_group)
marks_row = QHBoxLayout()
self.btn_marks_refresh = QPushButton("Refresh marks")
self.btn_marks_refresh.setToolTip(
"EN: Reload active runtime marks from backend (prunes expired).\n"
"RU: Обновить активные runtime-метки из backend (просроченные удаляются)."
)
self.btn_marks_refresh.clicked.connect(self.refresh_appmarks_items)
marks_row.addWidget(self.btn_marks_refresh)
self.btn_marks_unmark = QPushButton("Unmark selected")
self.btn_marks_unmark.setToolTip(
"EN: Remove routing marks for selected items (does not necessarily stop the app).\n"
"RU: Удалить метки маршрутизации для выбранных элементов (не обязательно останавливает приложение)."
)
self.btn_marks_unmark.clicked.connect(self.on_appmarks_unmark_selected)
marks_row.addWidget(self.btn_marks_unmark)
marks_row.addStretch(1)
marks_layout.addLayout(marks_row)
self.lst_marks = QListWidget()
self.lst_marks.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.lst_marks.setToolTip(
"EN: Active runtime marks. Stored by backend with TTL.\n"
"RU: Активные runtime-метки. Хранятся backend с TTL."
)
self.lst_marks.setFixedHeight(140)
marks_layout.addWidget(self.lst_marks)
self.lbl_marks = QLabel("Active marks: —")
self.lbl_marks.setStyleSheet("color: gray;")
marks_layout.addWidget(self.lbl_marks)
tab_apps_layout.addWidget(marks_group)
scopes_group = QGroupBox("Active svpn units (systemd --user)")
scopes_layout = QVBoxLayout(scopes_group)
@@ -546,6 +584,7 @@ RU: Применяет policy-rules и проверяет health. При оши
QtCore.QTimer.singleShot(0, self.refresh_state)
QtCore.QTimer.singleShot(0, self.refresh_app_profiles)
QtCore.QTimer.singleShot(0, self.refresh_appmarks_counts)
QtCore.QTimer.singleShot(0, self.refresh_appmarks_items)
QtCore.QTimer.singleShot(0, self.refresh_running_scopes)
# EN: Auto-refresh runtime marks/units while dialog is open.
@@ -561,6 +600,7 @@ RU: Применяет policy-rules и проверяет health. При оши
try:
# Only refresh units list when Apps(runtime) tab is visible.
if int(self.tabs.currentIndex() or 0) == 1:
self.refresh_appmarks_items(quiet=True)
self.refresh_running_scopes(quiet=True)
except Exception:
pass
@@ -1116,6 +1156,96 @@ RU: Применяет policy-rules и проверяет health. При оши
except Exception as e:
self.lbl_app_counts.setText(f"Marks: error: {e}")
def refresh_appmarks_items(self, quiet: bool = False) -> None:
def work() -> None:
items = list(self.ctrl.traffic_appmarks_items() or [])
self.lst_marks.clear()
vpn = 0
direct = 0
for it in items:
tgt = (getattr(it, "target", "") or "").strip().lower()
if tgt == "vpn":
vpn += 1
elif tgt == "direct":
direct += 1
mid = int(getattr(it, "id", 0) or 0)
app_key = (getattr(it, "app_key", "") or "").strip()
unit = (getattr(it, "unit", "") or "").strip()
cmd = (getattr(it, "command", "") or "").strip()
rem = int(getattr(it, "remaining_sec", 0) or 0)
rem_h = rem // 3600
rem_m = (rem % 3600) // 60
rem_s = rem % 60
rem_txt = f"{rem_h:02d}:{rem_m:02d}:{rem_s:02d}"
label = f"{tgt} {app_key or unit or mid} (ttl {rem_txt})"
q = QListWidgetItem(label)
q.setToolTip(
(
f"id: {mid}\n"
f"target: {tgt}\n"
f"app_key: {app_key}\n"
f"unit: {unit}\n"
f"remaining: {rem}s\n\n"
f"{cmd}"
).strip()
)
q.setData(QtCore.Qt.UserRole, it)
self.lst_marks.addItem(q)
self.lbl_marks.setText(f"Active marks: {len(items)} (VPN={vpn}, Direct={direct})")
self.btn_marks_unmark.setEnabled(self.lst_marks.count() > 0)
if quiet:
try:
work()
except Exception as e:
self.lbl_marks.setText(f"Active marks: error: {e}")
return
self._safe(work, title="Refresh marks error")
def on_appmarks_unmark_selected(self) -> None:
sel = list(self.lst_marks.selectedItems() or [])
if not sel:
return
# Convert selection to (target,id).
pairs: list[tuple[str, int]] = []
for it in sel:
obj = it.data(QtCore.Qt.UserRole)
tgt = (getattr(obj, "target", "") or "").strip().lower()
mid = int(getattr(obj, "id", 0) or 0)
if tgt in ("vpn", "direct") and mid > 0:
pairs.append((tgt, mid))
if not pairs:
return
def work() -> None:
if QMessageBox.question(
self,
"Unmark selected",
"Remove routing marks for selected items?\n\n"
+ "\n".join([f"{t}:{i}" for (t, i) in pairs[:20]])
+ ("\n..." if len(pairs) > 20 else ""),
) != QMessageBox.StandardButton.Yes:
return
for (tgt, mid) in pairs:
res = self.ctrl.traffic_appmarks_apply(op="del", target=tgt, cgroup=str(mid))
if not res.ok:
raise RuntimeError(res.message or f"unmark failed: {tgt}:{mid}")
self._set_action_status(f"Unmarked: {len(pairs)} item(s)", ok=True)
self.refresh_appmarks_items(quiet=True)
self.refresh_appmarks_counts()
self._safe(work, title="Unmark selected error")
def _refresh_last_scope_ui(self) -> None:
unit = (self._last_app_unit or "").strip()
target = (self._last_app_target or "").strip().lower()