ui: allow appmark by PID (no launch)
This commit is contained in:
@@ -393,6 +393,27 @@ RU: Восстанавливает маршруты/nft из последнег
|
|||||||
row_ttl.addStretch(1)
|
row_ttl.addStretch(1)
|
||||||
run_layout.addLayout(row_ttl)
|
run_layout.addLayout(row_ttl)
|
||||||
|
|
||||||
|
pid_group = QGroupBox("Mark existing PID (no launch)")
|
||||||
|
pid_layout = QHBoxLayout(pid_group)
|
||||||
|
pid_layout.addWidget(QLabel("PID"))
|
||||||
|
self.ed_app_pid = QLineEdit()
|
||||||
|
self.ed_app_pid.setPlaceholderText("e.g. 12345")
|
||||||
|
self.ed_app_pid.setToolTip(
|
||||||
|
"EN: Apply a runtime mark to an already running process.\n"
|
||||||
|
"EN: Reads /proc/<pid>/cgroup to get a cgroupv2 path.\n"
|
||||||
|
"RU: Применить runtime-метку к уже запущенному процессу.\n"
|
||||||
|
"RU: Читает /proc/<pid>/cgroup чтобы получить cgroupv2 path."
|
||||||
|
)
|
||||||
|
pid_layout.addWidget(self.ed_app_pid, stretch=1)
|
||||||
|
self.btn_app_mark_pid = QPushButton("Apply mark")
|
||||||
|
self.btn_app_mark_pid.setToolTip(
|
||||||
|
"EN: Apply routing mark to the PID (does not launch/stop the app).\n"
|
||||||
|
"RU: Применить метку маршрутизации к PID (не запускает/не останавливает приложение)."
|
||||||
|
)
|
||||||
|
self.btn_app_mark_pid.clicked.connect(self.on_app_mark_pid)
|
||||||
|
pid_layout.addWidget(self.btn_app_mark_pid)
|
||||||
|
run_layout.addWidget(pid_group)
|
||||||
|
|
||||||
row_btn = QHBoxLayout()
|
row_btn = QHBoxLayout()
|
||||||
self.btn_app_run = QPushButton("Run + apply mark")
|
self.btn_app_run = QPushButton("Run + apply mark")
|
||||||
self.btn_app_run.clicked.connect(self.on_app_run)
|
self.btn_app_run.clicked.connect(self.on_app_run)
|
||||||
@@ -1650,7 +1671,7 @@ RU: Применяет policy-rules и проверяет health. При оши
|
|||||||
self.lbl_app_last.setText("Last scope: —")
|
self.lbl_app_last.setText("Last scope: —")
|
||||||
|
|
||||||
self.btn_app_stop_last.setEnabled(bool(unit))
|
self.btn_app_stop_last.setEnabled(bool(unit))
|
||||||
self.btn_app_unmark_last.setEnabled(bool(unit) and target in ("vpn", "direct") and cg_id > 0)
|
self.btn_app_unmark_last.setEnabled(target in ("vpn", "direct") and cg_id > 0)
|
||||||
|
|
||||||
def _set_last_scope(
|
def _set_last_scope(
|
||||||
self,
|
self,
|
||||||
@@ -1793,6 +1814,28 @@ RU: Применяет policy-rules и проверяет health. При оши
|
|||||||
return ""
|
return ""
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def _cmdline_from_pid(self, pid: int) -> str:
|
||||||
|
p = int(pid or 0)
|
||||||
|
if p <= 0:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
with open(f"/proc/{p}/cmdline", "rb") as f:
|
||||||
|
raw = f.read() or b""
|
||||||
|
parts = [x for x in raw.split(b"\x00") if x]
|
||||||
|
out = " ".join([x.decode("utf-8", errors="replace") for x in parts])
|
||||||
|
return out.strip()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _exe_from_pid(self, pid: int) -> str:
|
||||||
|
p = int(pid or 0)
|
||||||
|
if p <= 0:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
return os.readlink(f"/proc/{p}/exe").strip()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
def _control_group_for_unit_retry(self, unit: str, *, timeout_sec: float = 2.0) -> str:
|
def _control_group_for_unit_retry(self, unit: str, *, timeout_sec: float = 2.0) -> str:
|
||||||
u = (unit or "").strip()
|
u = (unit or "").strip()
|
||||||
if not u:
|
if not u:
|
||||||
@@ -1833,6 +1876,61 @@ RU: Применяет policy-rules и проверяет health. При оши
|
|||||||
).strip()
|
).strip()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def on_app_mark_pid(self) -> None:
|
||||||
|
def work() -> None:
|
||||||
|
raw = (self.ed_app_pid.text() or "").strip()
|
||||||
|
if not raw:
|
||||||
|
QMessageBox.warning(self, "Missing PID", "Please enter a PID first.")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
pid = int(raw)
|
||||||
|
except Exception:
|
||||||
|
QMessageBox.warning(self, "Invalid PID", f"PID must be an integer: {raw!r}")
|
||||||
|
return
|
||||||
|
if pid <= 0:
|
||||||
|
QMessageBox.warning(self, "Invalid PID", f"PID must be > 0: {pid}")
|
||||||
|
return
|
||||||
|
|
||||||
|
cg = self._cgroup_path_from_pid(pid)
|
||||||
|
if not cg:
|
||||||
|
raise RuntimeError(f"failed to read cgroup for pid={pid} (process may not exist)")
|
||||||
|
|
||||||
|
cmdline = self._cmdline_from_pid(pid) or f"pid={pid}"
|
||||||
|
app_key = self._exe_from_pid(pid) or self._infer_app_key_from_cmdline(cmdline) or f"pid:{pid}"
|
||||||
|
|
||||||
|
target = "vpn" if self.rad_app_vpn.isChecked() else "direct"
|
||||||
|
ttl_sec = int(self.spn_app_ttl.value()) * 3600
|
||||||
|
|
||||||
|
self._append_app_log(f"[pid] mark: pid={pid} target={target} ttl={ttl_sec}s")
|
||||||
|
self._append_app_log(f"[pid] cgroup: {cg}")
|
||||||
|
if cmdline:
|
||||||
|
self._append_app_log(f"[pid] cmdline: {cmdline}")
|
||||||
|
|
||||||
|
res = self.ctrl.traffic_appmarks_apply(
|
||||||
|
op="add",
|
||||||
|
target=target,
|
||||||
|
cgroup=cg,
|
||||||
|
unit="",
|
||||||
|
command=cmdline,
|
||||||
|
app_key=app_key,
|
||||||
|
timeout_sec=ttl_sec,
|
||||||
|
)
|
||||||
|
if not res.ok:
|
||||||
|
self._append_app_log(f"[pid] ERROR: {res.message}")
|
||||||
|
self._set_action_status(f"PID mark failed: {res.message}", ok=False)
|
||||||
|
QMessageBox.critical(self, "Mark PID error", res.message or "mark failed")
|
||||||
|
return
|
||||||
|
|
||||||
|
self._append_app_log(f"[pid] OK: {res.message} cgroup_id={res.cgroup_id} timeout={res.timeout_sec}s")
|
||||||
|
self._set_action_status(f"PID marked: target={target} cgroup_id={res.cgroup_id}", ok=True)
|
||||||
|
self._set_last_scope(unit="", target=target, app_key=app_key, cmdline=cmdline, cgroup_id=int(res.cgroup_id or 0))
|
||||||
|
|
||||||
|
self.refresh_appmarks_counts()
|
||||||
|
self.refresh_appmarks_items(quiet=True)
|
||||||
|
self.refresh_app_profiles(quiet=True)
|
||||||
|
|
||||||
|
self._safe(work, title="Mark PID error")
|
||||||
|
|
||||||
def on_app_run(self) -> None:
|
def on_app_run(self) -> None:
|
||||||
def work() -> None:
|
def work() -> None:
|
||||||
cmdline = (self.ed_app_cmd.text() or "").strip()
|
cmdline = (self.ed_app_cmd.text() or "").strip()
|
||||||
@@ -2024,14 +2122,14 @@ RU: Применяет policy-rules и проверяет health. При оши
|
|||||||
unit = (self._last_app_unit or "").strip()
|
unit = (self._last_app_unit or "").strip()
|
||||||
target = (self._last_app_target or "").strip().lower()
|
target = (self._last_app_target or "").strip().lower()
|
||||||
cg_id = int(self._last_app_cgroup_id or 0)
|
cg_id = int(self._last_app_cgroup_id or 0)
|
||||||
if not unit or target not in ("vpn", "direct") or cg_id <= 0:
|
if target not in ("vpn", "direct") or cg_id <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
def work() -> None:
|
def work() -> None:
|
||||||
if QMessageBox.question(
|
if QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Unmark last",
|
"Unmark last",
|
||||||
f"Remove routing mark for last scope?\n\nunit={unit}\ntarget={target}\ncgroup_id={cg_id}",
|
f"Remove routing mark for last item?\n\nunit={unit or '-'}\ntarget={target}\ncgroup_id={cg_id}",
|
||||||
) != QMessageBox.StandardButton.Yes:
|
) != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user