platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
161
selective-vpn-gui/controllers/routes_controller.py
Normal file
161
selective-vpn-gui/controllers/routes_controller.py
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import cast
|
||||
|
||||
from api_client import CmdResult, Event
|
||||
|
||||
from .views import ActionView, RoutesNftProgressView, RoutesResolveSummaryView, ServiceAction
|
||||
|
||||
|
||||
class RoutesControllerMixin:
|
||||
def routes_service_action(self, action: str) -> ActionView:
|
||||
act = action.strip().lower()
|
||||
if act not in ("start", "stop", "restart"):
|
||||
raise ValueError(f"Invalid routes action: {action}")
|
||||
res = self.client.routes_service(cast(ServiceAction, act))
|
||||
return ActionView(ok=res.ok, pretty_text=self._pretty_cmd(res))
|
||||
|
||||
def routes_clear(self) -> ActionView:
|
||||
res = self.client.routes_clear()
|
||||
return ActionView(ok=res.ok, pretty_text=self._pretty_cmd(res))
|
||||
|
||||
def routes_cache_restore(self) -> ActionView:
|
||||
res = self.client.routes_cache_restore()
|
||||
return ActionView(ok=res.ok, pretty_text=self._pretty_cmd(res))
|
||||
|
||||
def routes_precheck_debug(self, run_now: bool = True) -> ActionView:
|
||||
res = self.client.routes_precheck_debug(run_now=run_now)
|
||||
return ActionView(ok=res.ok, pretty_text=self._pretty_cmd(res))
|
||||
|
||||
def routes_fix_policy_route(self) -> ActionView:
|
||||
res = self.client.routes_fix_policy_route()
|
||||
return ActionView(ok=res.ok, pretty_text=self._pretty_cmd(res))
|
||||
|
||||
def routes_timer_enabled(self) -> bool:
|
||||
st = self.client.routes_timer_get()
|
||||
return bool(st.enabled)
|
||||
|
||||
def routes_timer_set(self, enabled: bool) -> ActionView:
|
||||
res = self.client.routes_timer_set(bool(enabled))
|
||||
return ActionView(ok=res.ok, pretty_text=self._pretty_cmd(res))
|
||||
|
||||
def routes_resolve_summary_view(self) -> RoutesResolveSummaryView:
|
||||
dump = self.client.trace_get("full")
|
||||
lines = list(getattr(dump, "lines", []) or [])
|
||||
line = ""
|
||||
for raw in reversed(lines):
|
||||
s = str(raw or "")
|
||||
if "resolve summary:" in s:
|
||||
line = s
|
||||
break
|
||||
if not line:
|
||||
return RoutesResolveSummaryView(
|
||||
available=False,
|
||||
text="Resolve summary: no data yet",
|
||||
recheck_text="Timeout recheck: —",
|
||||
color="gray",
|
||||
recheck_color="gray",
|
||||
)
|
||||
|
||||
tail = line.split("resolve summary:", 1)[1]
|
||||
pairs: dict[str, int] = {}
|
||||
for m in re.finditer(r"([a-zA-Z0-9_]+)=(-?\d+)", tail):
|
||||
k = str(m.group(1) or "").strip().lower()
|
||||
try:
|
||||
pairs[k] = int(m.group(2))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
unique_ips = int(pairs.get("unique_ips", 0))
|
||||
direct_ips = int(pairs.get("direct_ips", 0))
|
||||
wildcard_ips = int(pairs.get("wildcard_ips", 0))
|
||||
unresolved = int(pairs.get("unresolved", 0))
|
||||
unresolved_live = int(pairs.get("unresolved_live", 0))
|
||||
unresolved_suppressed = int(pairs.get("unresolved_suppressed", 0))
|
||||
q_hits = int(pairs.get("quarantine_hits", 0))
|
||||
dns_attempts = int(pairs.get("dns_attempts", 0))
|
||||
dns_timeout = int(pairs.get("dns_timeout", 0))
|
||||
dns_cooldown_skips = int(pairs.get("dns_cooldown_skips", 0))
|
||||
live_batch_target = int(pairs.get("live_batch_target", 0))
|
||||
live_batch_deferred = int(pairs.get("live_batch_deferred", 0))
|
||||
live_batch_p1 = int(pairs.get("live_batch_p1", 0))
|
||||
live_batch_p2 = int(pairs.get("live_batch_p2", 0))
|
||||
live_batch_p3 = int(pairs.get("live_batch_p3", 0))
|
||||
live_batch_nxheavy_pct = int(pairs.get("live_batch_nxheavy_pct", 0))
|
||||
live_batch_nxheavy_skip = int(pairs.get("live_batch_nxheavy_skip", 0))
|
||||
|
||||
r_checked = int(pairs.get("timeout_recheck_checked", 0))
|
||||
r_recovered = int(pairs.get("timeout_recheck_recovered", 0))
|
||||
r_recovered_ips = int(pairs.get("timeout_recheck_recovered_ips", 0))
|
||||
r_still_timeout = int(pairs.get("timeout_recheck_still_timeout", 0))
|
||||
r_now_nx = int(pairs.get("timeout_recheck_now_nxdomain", 0))
|
||||
r_now_tmp = int(pairs.get("timeout_recheck_now_temporary", 0))
|
||||
|
||||
text = (
|
||||
f"Resolve: ips={unique_ips} (direct={direct_ips}, wildcard={wildcard_ips}, "
|
||||
f"+recheck_ips={r_recovered_ips}) | unresolved={unresolved} "
|
||||
f"(live={unresolved_live}, suppressed={unresolved_suppressed}) | "
|
||||
f"quarantine_hits={q_hits} | dns_timeout={dns_timeout} "
|
||||
f"| cooldown_skips={dns_cooldown_skips} | attempts={dns_attempts} "
|
||||
f"| live_batch={live_batch_target} deferred={live_batch_deferred} "
|
||||
f"(p1={live_batch_p1}, p2={live_batch_p2}, p3={live_batch_p3}, nx_pct={live_batch_nxheavy_pct}, nx_skip={live_batch_nxheavy_skip})"
|
||||
)
|
||||
recheck_text = (
|
||||
f"Timeout recheck: checked={r_checked} recovered={r_recovered} "
|
||||
f"still_timeout={r_still_timeout} now_nxdomain={r_now_nx} now_temporary={r_now_tmp}"
|
||||
)
|
||||
|
||||
color = "green" if unresolved < 4000 else ("#b58900" if unresolved < 10000 else "red")
|
||||
if dns_timeout > 500 and color == "green":
|
||||
color = "#b58900"
|
||||
if live_batch_p3 > 0 and (live_batch_p1+live_batch_p2) > 0:
|
||||
ratio = float(live_batch_p3) / float(live_batch_p1 + live_batch_p2 + live_batch_p3)
|
||||
if ratio > 0.8:
|
||||
color = "#b58900" if color == "green" else color
|
||||
if ratio > 0.95:
|
||||
color = "red"
|
||||
recheck_color = "green" if r_still_timeout <= 20 else ("#b58900" if r_still_timeout <= 100 else "red")
|
||||
return RoutesResolveSummaryView(
|
||||
available=True,
|
||||
text=text,
|
||||
recheck_text=recheck_text,
|
||||
color=color,
|
||||
recheck_color=recheck_color,
|
||||
)
|
||||
|
||||
|
||||
def routes_nft_progress_from_event(self, ev: Event) -> RoutesNftProgressView:
|
||||
"""
|
||||
Превращает Event(kind='routes_nft_progress') в удобную модель
|
||||
для прогресс-бара/лейбла.
|
||||
"""
|
||||
payload = (
|
||||
getattr(ev, "data", None)
|
||||
or getattr(ev, "payload", None)
|
||||
or getattr(ev, "extra", None)
|
||||
or {}
|
||||
)
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
payload = {}
|
||||
|
||||
try:
|
||||
percent = int(payload.get("percent", 0))
|
||||
except Exception:
|
||||
percent = 0
|
||||
|
||||
msg = str(payload.get("message", "")) if payload is not None else ""
|
||||
if not msg:
|
||||
msg = "Updating nft set…"
|
||||
|
||||
active = 0 <= percent < 100
|
||||
|
||||
return RoutesNftProgressView(
|
||||
percent=percent,
|
||||
message=msg,
|
||||
active=active,
|
||||
)
|
||||
|
||||
# -------- DNS / SmartDNS --------
|
||||
Reference in New Issue
Block a user