245 lines
8.8 KiB
Python
245 lines
8.8 KiB
Python
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")
|