ui: run selected app profile (systemd-run + appmark refresh)
This commit is contained in:
@@ -267,6 +267,14 @@ RU: Восстанавливает маршруты/nft из последнег
|
||||
self.btn_app_profile_load.clicked.connect(self.on_app_profile_load)
|
||||
row_prof_btn.addWidget(self.btn_app_profile_load)
|
||||
|
||||
self.btn_app_profile_run = QPushButton("Run profile")
|
||||
self.btn_app_profile_run.setToolTip(
|
||||
"EN: Launch selected profile via systemd-run --user and apply routing mark.\n"
|
||||
"RU: Запустить выбранный профиль через systemd-run --user и применить метку маршрутизации."
|
||||
)
|
||||
self.btn_app_profile_run.clicked.connect(self.on_app_profile_run)
|
||||
row_prof_btn.addWidget(self.btn_app_profile_run)
|
||||
|
||||
self.btn_app_profile_delete = QPushButton("Delete profile")
|
||||
self.btn_app_profile_delete.setToolTip(
|
||||
"EN: Delete selected saved profile.\n"
|
||||
@@ -1025,6 +1033,105 @@ RU: Применяет policy-rules и проверяет health. При оши
|
||||
# Fallback: first token
|
||||
return (cmd.split() or [""])[0].strip()
|
||||
|
||||
def _launch_and_mark(
|
||||
self,
|
||||
*,
|
||||
cmdline: str,
|
||||
target: str,
|
||||
ttl_sec: int,
|
||||
app_key: str = "",
|
||||
) -> None:
|
||||
cmdline = (cmdline or "").strip()
|
||||
if not cmdline:
|
||||
raise ValueError("empty command")
|
||||
tgt = (target or "").strip().lower()
|
||||
if tgt not in ("vpn", "direct"):
|
||||
raise ValueError("invalid target")
|
||||
ttl = int(ttl_sec or 0)
|
||||
if ttl <= 0:
|
||||
ttl = int(self.spn_app_ttl.value()) * 3600
|
||||
key = (app_key or "").strip() or self._infer_app_key_from_cmdline(cmdline)
|
||||
|
||||
# EN: If we already have a running unit for the same app_key+target, refresh mark instead of spawning.
|
||||
# RU: Если уже есть запущенный unit для того же app_key+target — обновляем метку, не плодим инстансы.
|
||||
try:
|
||||
items = list(self.ctrl.traffic_appmarks_items() or [])
|
||||
except Exception:
|
||||
items = []
|
||||
for it in items:
|
||||
if (getattr(it, "target", "") or "").strip().lower() != tgt:
|
||||
continue
|
||||
if (getattr(it, "app_key", "") or "").strip() != key:
|
||||
continue
|
||||
unit = (getattr(it, "unit", "") or "").strip()
|
||||
if not unit:
|
||||
continue
|
||||
code, out = self._systemctl_user(["is-active", unit])
|
||||
if code == 0 and (out or "").strip().lower() == "active":
|
||||
cg = self._effective_cgroup_for_unit_retry(unit, timeout_sec=3.0)
|
||||
self._append_app_log(
|
||||
f"[profile] already running: app={key} target={tgt} unit={unit} (refreshing mark)"
|
||||
)
|
||||
res = self.ctrl.traffic_appmarks_apply(
|
||||
op="add",
|
||||
target=tgt,
|
||||
cgroup=cg,
|
||||
unit=unit,
|
||||
command=cmdline,
|
||||
app_key=key,
|
||||
timeout_sec=ttl,
|
||||
)
|
||||
if not res.ok:
|
||||
raise RuntimeError(res.message or "appmark refresh failed")
|
||||
self._set_action_status(
|
||||
f"App mark refreshed: target={tgt} cgroup_id={res.cgroup_id}",
|
||||
ok=True,
|
||||
)
|
||||
self._set_last_scope(
|
||||
unit=unit,
|
||||
target=tgt,
|
||||
app_key=key,
|
||||
cmdline=cmdline,
|
||||
cgroup_id=int(res.cgroup_id or 0),
|
||||
)
|
||||
self.refresh_appmarks_items(quiet=True)
|
||||
self.refresh_appmarks_counts()
|
||||
self.refresh_running_scopes(quiet=True)
|
||||
return
|
||||
|
||||
unit = f"svpn-{tgt}-{int(time.time())}.service"
|
||||
self._append_app_log(f"[profile] launching: app={key or '-'} target={tgt} ttl={ttl}s unit={unit}")
|
||||
cg, out = self._run_systemd_unit(cmdline, unit=unit)
|
||||
if out:
|
||||
self._append_app_log(f"[profile] systemd-run:\n{out}")
|
||||
self._append_app_log(f"[profile] ControlGroup: {cg}")
|
||||
self._set_last_scope(unit=unit, target=tgt, app_key=key, cmdline=cmdline, cgroup_id=0)
|
||||
|
||||
res = self.ctrl.traffic_appmarks_apply(
|
||||
op="add",
|
||||
target=tgt,
|
||||
cgroup=cg,
|
||||
unit=unit,
|
||||
command=cmdline,
|
||||
app_key=key,
|
||||
timeout_sec=ttl,
|
||||
)
|
||||
if not res.ok:
|
||||
raise RuntimeError(res.message or "appmark apply failed")
|
||||
|
||||
self._append_app_log(f"[appmarks] OK: {res.message} cgroup_id={res.cgroup_id} timeout={res.timeout_sec}s")
|
||||
self._set_action_status(f"App mark added: target={tgt} cgroup_id={res.cgroup_id}", ok=True)
|
||||
self._set_last_scope(
|
||||
unit=unit,
|
||||
target=tgt,
|
||||
app_key=key,
|
||||
cmdline=cmdline,
|
||||
cgroup_id=int(res.cgroup_id or 0),
|
||||
)
|
||||
self.refresh_appmarks_items(quiet=True)
|
||||
self.refresh_appmarks_counts()
|
||||
self.refresh_running_scopes(quiet=True)
|
||||
|
||||
def _selected_app_profile(self):
|
||||
it = self.lst_app_profiles.currentItem()
|
||||
if not it:
|
||||
@@ -1142,6 +1249,25 @@ RU: Применяет policy-rules и проверяет health. При оши
|
||||
|
||||
self._safe(work, title="Load profile error")
|
||||
|
||||
def on_app_profile_run(self) -> None:
|
||||
prof = self._selected_app_profile()
|
||||
if prof is None:
|
||||
return
|
||||
|
||||
def work() -> None:
|
||||
cmd = (getattr(prof, "command", "") or "").strip()
|
||||
target = (getattr(prof, "target", "") or "").strip().lower()
|
||||
ttl = int(getattr(prof, "ttl_sec", 0) or 0)
|
||||
app_key = (getattr(prof, "app_key", "") or "").strip()
|
||||
if not cmd:
|
||||
raise RuntimeError("profile has empty command")
|
||||
if target not in ("vpn", "direct"):
|
||||
target = "vpn"
|
||||
|
||||
self._launch_and_mark(cmdline=cmd, target=target, ttl_sec=ttl, app_key=app_key)
|
||||
|
||||
self._safe(work, title="Run profile error")
|
||||
|
||||
def on_app_profile_delete(self) -> None:
|
||||
prof = self._selected_app_profile()
|
||||
if prof is None:
|
||||
|
||||
Reference in New Issue
Block a user