306 lines
14 KiB
Python
306 lines
14 KiB
Python
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")
|