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")