from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QFormLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QPlainTextEdit, QListView, QPushButton, QStackedWidget, QStyle, QTabWidget, QToolButton, QVBoxLayout, QWidget, QComboBox, ) class UITabsMainMixin: def _build_ui(self) -> None: root = QWidget() root_layout = QVBoxLayout(root) root.setLayout(root_layout) self.setCentralWidget(root) # top bar --------------------------------------------------------- top = QHBoxLayout() root_layout.addLayout(top) # клик по этому баннеру показывает whoami self.btn_login_banner = QPushButton("AdGuard VPN: —") self.btn_login_banner.setFlat(True) self.btn_login_banner.setStyleSheet( "text-align: left; border: none; color: gray;" ) self.btn_login_banner.clicked.connect(self.on_login_banner_clicked) top.addWidget(self.btn_login_banner, stretch=1) self.btn_auth = QPushButton("Login") self.btn_auth.clicked.connect(self.on_auth_button) top.addWidget(self.btn_auth) self.btn_refresh_all = QPushButton("Refresh all") self.btn_refresh_all.clicked.connect(self.refresh_everything) top.addWidget(self.btn_refresh_all) # tabs ------------------------------------------------------------- self.tabs = QTabWidget() root_layout.addWidget(self.tabs, stretch=1) self._build_tab_status() self._build_tab_vpn() self._build_tab_singbox() self._build_tab_multiif() self._build_tab_routes() self._build_tab_dns() self._build_tab_domains() self._build_tab_trace() # ---------------- STATUS TAB ---------------- def _build_tab_status(self) -> None: tab = QWidget() layout = QVBoxLayout(tab) grid = QFormLayout() layout.addLayout(grid) self.st_timestamp = QLabel("—") self.st_counts = QLabel("—") self.st_iface = QLabel("—") self.st_route = QLabel("—") self.st_routes_service = QLabel("—") self.st_smartdns_service = QLabel("—") self.st_vpn_service = QLabel("—") grid.addRow("Timestamp:", self.st_timestamp) grid.addRow("Counts:", self.st_counts) grid.addRow("Iface / table / mark:", self.st_iface) grid.addRow("Policy route:", self.st_route) grid.addRow("Routes service:", self.st_routes_service) grid.addRow("SmartDNS:", self.st_smartdns_service) grid.addRow("VPN service:", self.st_vpn_service) btns = QHBoxLayout() layout.addLayout(btns) btn_refresh = QPushButton("Refresh") btn_refresh.clicked.connect(self.refresh_status_tab) btns.addWidget(btn_refresh) btns.addStretch(1) self.tabs.addTab(tab, "Status") # ---------------- VPN TAB ---------------- def _build_tab_vpn(self) -> None: tab = QWidget() self.tab_vpn = tab # нужно, чтобы переключаться сюда из шапки layout = QVBoxLayout(tab) # stack: main vs login-flow page self.vpn_stack = QStackedWidget() layout.addWidget(self.vpn_stack, stretch=1) # ---- main page page_main = QWidget() main_layout = QVBoxLayout(page_main) # Autoconnect group auto_group = QGroupBox("Autoconnect (AdGuardVPN autoloop)") auto_layout = QHBoxLayout(auto_group) self.btn_autoconnect_toggle = QPushButton("Enable autoconnect") self.btn_autoconnect_toggle.clicked.connect(self.on_toggle_autoconnect) auto_layout.addWidget(self.btn_autoconnect_toggle) auto_layout.addStretch(1) # справа текст "unit: active/inactive" с цветом self.lbl_autoconnect_state = QLabel("unit: —") self.lbl_autoconnect_state.setStyleSheet("color: gray;") auto_layout.addWidget(self.lbl_autoconnect_state) main_layout.addWidget(auto_group) # Locations group loc_group = QGroupBox("Location") loc_layout = QVBoxLayout(loc_group) loc_row = QHBoxLayout() loc_layout.addLayout(loc_row) self.cmb_locations = QComboBox() # компактный popup со скроллом, а не на весь экран self.cmb_locations.setMaxVisibleItems(12) self.cmb_locations.setStyleSheet("combobox-popup: 0;") self.cmb_locations.setFocusPolicy(Qt.StrongFocus) view = QListView() view.setUniformItemSizes(True) self.cmb_locations.setView(view) self.cmb_locations.activated.connect(self.on_location_activated) self.cmb_locations.installEventFilter(self) view.installEventFilter(self) loc_row.addWidget(self.cmb_locations, stretch=1) self.cmb_locations_sort = QComboBox() self.cmb_locations_sort.addItem("Sort: Ping", "ping") self.cmb_locations_sort.addItem("Sort: Ping (slow first)", "ping_desc") self.cmb_locations_sort.addItem("Sort: Name", "name") self.cmb_locations_sort.addItem("Sort: Name (Z-A)", "name_desc") self.cmb_locations_sort.currentIndexChanged.connect( self.on_locations_sort_changed ) loc_row.addWidget(self.cmb_locations_sort) self.btn_locations_refresh = QToolButton() self.btn_locations_refresh.setAutoRaise(True) self.btn_locations_refresh.setIcon( self.style().standardIcon(QStyle.SP_BrowserReload) ) self.btn_locations_refresh.setToolTip("Refresh locations now") self.btn_locations_refresh.setCursor(Qt.PointingHandCursor) self.btn_locations_refresh.setFocusPolicy(Qt.NoFocus) self.btn_locations_refresh.clicked.connect(self.on_locations_refresh_click) loc_row.addWidget(self.btn_locations_refresh) self.lbl_locations_meta = QLabel("Locations: loading...") self.lbl_locations_meta.setStyleSheet("color: gray;") loc_layout.addWidget(self.lbl_locations_meta) self.lbl_vpn_egress = QLabel("Egress: n/a") self.lbl_vpn_egress.setStyleSheet("color: gray;") loc_layout.addWidget(self.lbl_vpn_egress) main_layout.addWidget(loc_group) # Status output self.txt_vpn = QPlainTextEdit() self.txt_vpn.setReadOnly(True) main_layout.addWidget(self.txt_vpn, stretch=1) self.vpn_stack.addWidget(page_main) # ---- login page page_login = QWidget() lf_layout = QVBoxLayout(page_login) top = QHBoxLayout() lf_layout.addLayout(top) self.lbl_login_flow_status = QLabel("Status: —") top.addWidget(self.lbl_login_flow_status) self.lbl_login_flow_email = QLabel("") self.lbl_login_flow_email.setStyleSheet("color: gray;") top.addWidget(self.lbl_login_flow_email) top.addStretch(1) # URL + buttons row row2 = QHBoxLayout() lf_layout.addLayout(row2) row2.addWidget(QLabel("URL:")) self.edit_login_url = QLineEdit() row2.addWidget(self.edit_login_url, stretch=1) self.btn_login_open = QPushButton("Open") self.btn_login_open.clicked.connect(self.on_login_open) row2.addWidget(self.btn_login_open) self.btn_login_copy = QPushButton("Copy") self.btn_login_copy.clicked.connect(self.on_login_copy) row2.addWidget(self.btn_login_copy) self.btn_login_check = QPushButton("Check") self.btn_login_check.clicked.connect(self.on_login_check) row2.addWidget(self.btn_login_check) self.btn_login_close = QPushButton("Cancel") self.btn_login_close.clicked.connect(self.on_login_cancel) row2.addWidget(self.btn_login_close) self.btn_login_stop = QPushButton("Stop session") self.btn_login_stop.clicked.connect(self.on_login_stop) row2.addWidget(self.btn_login_stop) # log text self.txt_login_flow = QPlainTextEdit() self.txt_login_flow.setReadOnly(True) lf_layout.addWidget(self.txt_login_flow, stretch=1) # bottom buttons bottom = QHBoxLayout() lf_layout.addLayout(bottom) # Start login визуально убираем, но объект оставим на всякий self.btn_login_start = QPushButton("Start login") self.btn_login_start.clicked.connect(self.on_start_login) self.btn_login_start.setVisible(False) bottom.addWidget(self.btn_login_start) btn_back = QPushButton("Back to VPN") btn_back.clicked.connect(lambda: self._show_vpn_page("main")) bottom.addWidget(btn_back) bottom.addStretch(1) self.vpn_stack.addWidget(page_login) self.tabs.addTab(tab, "AdGuardVPN")