platform: modularize api/gui, add docs-tests-web foundation, and refresh root config

This commit is contained in:
beckline
2026-03-26 22:40:54 +03:00
parent 0e2d7f61ea
commit 6a56d734c2
562 changed files with 70151 additions and 16423 deletions

View File

@@ -0,0 +1,305 @@
from __future__ import annotations
from PySide6.QtWidgets import (
QCheckBox,
QFormLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QLineEdit,
QListWidget,
QListWidgetItem,
QPlainTextEdit,
QPushButton,
QProgressBar,
QRadioButton,
QVBoxLayout,
QWidget,
)
class UITabsOtherMixin:
def _build_tab_routes(self) -> None:
tab = QWidget()
layout = QVBoxLayout(tab)
# --- Service actions ---
act_group = QGroupBox("Selective routes service")
act_layout = QHBoxLayout(act_group)
self.btn_routes_start = QPushButton("Start")
self.btn_routes_start.clicked.connect(
lambda: self.on_routes_action("start")
)
self.btn_routes_restart = QPushButton("Restart")
self.btn_routes_restart.clicked.connect(
lambda: self.on_routes_action("restart")
)
self.btn_routes_stop = QPushButton("Stop")
self.btn_routes_stop.clicked.connect(
lambda: self.on_routes_action("stop")
)
act_layout.addWidget(self.btn_routes_start)
act_layout.addWidget(self.btn_routes_restart)
act_layout.addWidget(self.btn_routes_stop)
act_layout.addStretch(1)
layout.addWidget(act_group)
# --- Timer / policy route ---
timer_group = QGroupBox("Timer")
timer_layout = QHBoxLayout(timer_group)
self.chk_timer = QCheckBox("Enable timer")
self.chk_timer.stateChanged.connect(self.on_toggle_timer)
timer_layout.addWidget(self.chk_timer)
self.btn_fix_policy = QPushButton("Fix policy route")
self.btn_fix_policy.clicked.connect(self.on_fix_policy_route)
timer_layout.addWidget(self.btn_fix_policy)
timer_layout.addStretch(1)
layout.addWidget(timer_group)
# --- Traffic mode relay ---
traffic_group = QGroupBox("Traffic mode relay")
traffic_layout = QVBoxLayout(traffic_group)
relay_row = QHBoxLayout()
self.btn_traffic_settings = QPushButton("Open traffic settings")
self.btn_traffic_settings.clicked.connect(self.on_open_traffic_settings)
relay_row.addWidget(self.btn_traffic_settings)
self.btn_traffic_test = QPushButton("Test mode")
self.btn_traffic_test.clicked.connect(self.on_test_traffic_mode)
relay_row.addWidget(self.btn_traffic_test)
self.btn_routes_prewarm = QPushButton("Prewarm wildcard now")
self.btn_routes_prewarm.setToolTip("""EN: Sends DNS queries for wildcard domains to prefill agvpn_dyn4 before traffic arrives.
RU: Делает DNS-запросы wildcard-доменов, чтобы заранее наполнить agvpn_dyn4.""")
self.btn_routes_prewarm.clicked.connect(self.on_smartdns_prewarm)
relay_row.addWidget(self.btn_routes_prewarm)
self.btn_routes_precheck_debug = QPushButton("Debug precheck now")
self.btn_routes_precheck_debug.setToolTip("""EN: Debug helper. Arms one-shot resolver precheck and requests routes restart now.
RU: Отладочный helper. Включает one-shot precheck резолвера и запрашивает restart routes.""")
self.btn_routes_precheck_debug.clicked.connect(self.on_routes_precheck_debug)
relay_row.addWidget(self.btn_routes_precheck_debug)
relay_row.addStretch(1)
traffic_layout.addLayout(relay_row)
self.chk_routes_prewarm_aggressive = QCheckBox("Aggressive prewarm (use subs)")
self.chk_routes_prewarm_aggressive.setToolTip("""EN: Aggressive mode also queries subs list. This can increase DNS load.
RU: Агрессивный режим дополнительно дергает subs список. Может увеличить нагрузку на DNS.""")
self.chk_routes_prewarm_aggressive.stateChanged.connect(self._on_prewarm_aggressive_changed)
traffic_layout.addWidget(self.chk_routes_prewarm_aggressive)
self.lbl_routes_prewarm_mode = QLabel("Prewarm mode: wildcard-only")
self.lbl_routes_prewarm_mode.setStyleSheet("color: gray;")
traffic_layout.addWidget(self.lbl_routes_prewarm_mode)
self._update_prewarm_mode_label()
self.lbl_traffic_mode_state = QLabel("Traffic mode: —")
self.lbl_traffic_mode_state.setStyleSheet("color: gray;")
traffic_layout.addWidget(self.lbl_traffic_mode_state)
self.lbl_traffic_mode_diag = QLabel("")
self.lbl_traffic_mode_diag.setStyleSheet("color: gray;")
traffic_layout.addWidget(self.lbl_traffic_mode_diag)
self.lbl_routes_resolve_summary = QLabel("Resolve summary: —")
self.lbl_routes_resolve_summary.setToolTip("""EN: Parsed from latest 'resolve summary' trace line.
RU: Берется из последней строки 'resolve summary' в trace.""")
self.lbl_routes_resolve_summary.setStyleSheet("color: gray;")
traffic_layout.addWidget(self.lbl_routes_resolve_summary)
self.lbl_routes_recheck_summary = QLabel("Timeout recheck: —")
self.lbl_routes_recheck_summary.setToolTip("""EN: Hidden timeout-recheck counters included in resolve summary.
RU: Счетчики скрытого timeout-recheck из итогового resolve summary.""")
self.lbl_routes_recheck_summary.setStyleSheet("color: gray;")
traffic_layout.addWidget(self.lbl_routes_recheck_summary)
layout.addWidget(traffic_group)
# --- NFT progress (agvpn4) ---
progress_row = QHBoxLayout()
self.routes_progress = QProgressBar()
self.routes_progress.setRange(0, 100)
self.routes_progress.setValue(0)
self.routes_progress.setFormat("") # текст выводим отдельным лейблом
self.routes_progress.setTextVisible(False)
self.routes_progress.setEnabled(False) # idle по умолчанию
self.lbl_routes_progress = QLabel("NFT: idle")
self.lbl_routes_progress.setStyleSheet("color: gray;")
progress_row.addWidget(self.routes_progress)
progress_row.addWidget(self.lbl_routes_progress)
layout.addLayout(progress_row)
# --- Log output ---
self.txt_routes = QPlainTextEdit()
self.txt_routes.setReadOnly(True)
layout.addWidget(self.txt_routes, stretch=1)
self.tabs.addTab(tab, "Routes")
# ---------------- DNS TAB ----------------
def _build_tab_dns(self) -> None:
tab = QWidget()
main_layout = QVBoxLayout(tab)
tip = QLabel("Tip: hover fields for help. Подсказка: наведи на элементы для описания.")
tip.setWordWrap(True)
tip.setStyleSheet("color: gray;")
main_layout.addWidget(tip)
resolver_group = QGroupBox("Resolver DNS")
resolver_group.setToolTip("""EN: Compact resolver DNS status. Open benchmark to test/apply upstreams.
RU: Компактный статус DNS резолвера. Открой benchmark для проверки/применения апстримов.""")
resolver_layout = QVBoxLayout(resolver_group)
row = QHBoxLayout()
self.btn_dns_benchmark = QPushButton("Open DNS benchmark")
self.btn_dns_benchmark.clicked.connect(self.on_open_dns_benchmark)
row.addWidget(self.btn_dns_benchmark)
row.addStretch(1)
resolver_layout.addLayout(row)
self.lbl_dns_resolver_upstreams = QLabel("Resolver upstreams: default[—, —] meta[—, —]")
self.lbl_dns_resolver_upstreams.setStyleSheet("color: gray;")
resolver_layout.addWidget(self.lbl_dns_resolver_upstreams)
self.lbl_dns_resolver_health = QLabel("Resolver health: —")
self.lbl_dns_resolver_health.setStyleSheet("color: gray;")
resolver_layout.addWidget(self.lbl_dns_resolver_health)
main_layout.addWidget(resolver_group)
smart_group = QGroupBox("SmartDNS")
smart_group.setToolTip("""EN: SmartDNS is used for wildcard domains in hybrid mode.
RU: SmartDNS используется для wildcard-доменов в hybrid режиме.""")
smart_layout = QVBoxLayout(smart_group)
smart_form = QFormLayout()
self.ent_smartdns_addr = QLineEdit()
self.ent_smartdns_addr.setToolTip("""EN: SmartDNS address in host#port format (example: 127.0.0.1#6053).
RU: Адрес SmartDNS в формате host#port (пример: 127.0.0.1#6053).""")
self.ent_smartdns_addr.setPlaceholderText("127.0.0.1#6053")
self.ent_smartdns_addr.textEdited.connect(self._schedule_dns_autosave)
smart_form.addRow("SmartDNS address", self.ent_smartdns_addr)
smart_layout.addLayout(smart_form)
self.chk_dns_via_smartdns = QCheckBox("Use SmartDNS for wildcard domains")
self.chk_dns_via_smartdns.setToolTip("""EN: Hybrid wildcard mode: wildcard domains resolve via SmartDNS, other lists resolve via direct upstreams.
RU: Hybrid wildcard режим: wildcard-домены резолвятся через SmartDNS, остальные списки через direct апстримы.""")
self.chk_dns_via_smartdns.stateChanged.connect(self.on_dns_mode_toggle)
smart_layout.addWidget(self.chk_dns_via_smartdns)
self.lbl_dns_mode_state = QLabel("Resolver mode: unknown")
self.lbl_dns_mode_state.setToolTip("""EN: Current resolver mode reported by API.
RU: Текущий режим резолвера по данным API.""")
smart_layout.addWidget(self.lbl_dns_mode_state)
self.chk_dns_unit_relay = QCheckBox("SmartDNS unit relay: OFF")
self.chk_dns_unit_relay.setToolTip("""EN: Starts/stops smartdns-local.service. Service state is independent from resolver mode.
RU: Запускает/останавливает smartdns-local.service. Состояние сервиса не равно режиму резолвера.""")
self.chk_dns_unit_relay.stateChanged.connect(self.on_smartdns_unit_toggle)
smart_layout.addWidget(self.chk_dns_unit_relay)
self.chk_dns_runtime_nftset = QCheckBox("SmartDNS runtime accelerator (nftset -> agvpn_dyn4): ON")
self.chk_dns_runtime_nftset.setToolTip("""EN: Optional accelerator: SmartDNS can add resolved IPs to agvpn_dyn4 in runtime (via nftset).
EN: Wildcard still works without it (resolver job + prewarm).
RU: Опциональный ускоритель: SmartDNS может добавлять IP в agvpn_dyn4 в runtime (через nftset).
RU: Wildcard работает и без него (resolver job + prewarm).""")
self.chk_dns_runtime_nftset.stateChanged.connect(self.on_smartdns_runtime_toggle)
smart_layout.addWidget(self.chk_dns_runtime_nftset)
self.lbl_dns_wildcard_source = QLabel("Wildcard source: resolver")
self.lbl_dns_wildcard_source.setToolTip("""EN: Where wildcard IPs come from: resolver job, SmartDNS runtime nftset, or both.
RU: Источник wildcard IP: резолвер, runtime nftset SmartDNS, или оба.""")
self.lbl_dns_wildcard_source.setStyleSheet("color: gray;")
smart_layout.addWidget(self.lbl_dns_wildcard_source)
main_layout.addWidget(smart_group)
main_layout.addStretch(1)
self.tabs.addTab(tab, "DNS")
# ---------------- DOMAINS TAB ----------------
def _build_tab_domains(self) -> None:
tab = QWidget()
main_layout = QHBoxLayout(tab)
left = QVBoxLayout()
main_layout.addLayout(left)
left.addWidget(QLabel("Files:"))
self.lst_files = QListWidget()
for name in (
"bases",
"meta-special",
"subs",
"static-ips",
"last-ips-map-direct",
"last-ips-map-wildcard",
"wildcard-observed-hosts",
"smartdns.conf",
):
QListWidgetItem(name, self.lst_files)
self.lst_files.setCurrentRow(0)
self.lst_files.itemSelectionChanged.connect(self.on_domains_load)
left.addWidget(self.lst_files)
self.btn_domains_save = QPushButton("Save file")
self.btn_domains_save.clicked.connect(self.on_domains_save)
left.addWidget(self.btn_domains_save)
left.addStretch(1)
right_layout = QVBoxLayout()
main_layout.addLayout(right_layout, stretch=1)
self.lbl_domains_info = QLabel("")
self.lbl_domains_info.setStyleSheet("color: gray;")
right_layout.addWidget(self.lbl_domains_info)
self.txt_domains = QPlainTextEdit()
right_layout.addWidget(self.txt_domains, stretch=1)
self.tabs.addTab(tab, "Domains")
# ---------------- TRACE TAB ----------------
def _build_tab_trace(self) -> None:
tab = QWidget()
layout = QVBoxLayout(tab)
top = QHBoxLayout()
layout.addLayout(top)
self.radio_trace_full = QRadioButton("Full")
self.radio_trace_full.setChecked(True)
self.radio_trace_full.toggled.connect(self.refresh_trace_tab)
top.addWidget(self.radio_trace_full)
self.radio_trace_gui = QRadioButton("Events")
self.radio_trace_gui.toggled.connect(self.refresh_trace_tab)
top.addWidget(self.radio_trace_gui)
self.radio_trace_smartdns = QRadioButton("SmartDNS")
self.radio_trace_smartdns.toggled.connect(self.refresh_trace_tab)
top.addWidget(self.radio_trace_smartdns)
btn_refresh = QPushButton("Refresh")
btn_refresh.clicked.connect(self.refresh_trace_tab)
top.addWidget(btn_refresh)
top.addStretch(1)
self.txt_trace = QPlainTextEdit()
self.txt_trace.setReadOnly(True)
layout.addWidget(self.txt_trace, stretch=1)
self.tabs.addTab(tab, "Trace")