Files
elmprodvpn/selective-vpn-gui/vpn_dashboard_qt.py

124 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env python3
from __future__ import annotations
import sys
from typing import Any
from PySide6.QtCore import QSettings, QTimer
from PySide6.QtWidgets import QApplication, QMainWindow
from api_client import ApiClient
from dashboard_controller import DashboardController
from main_window.runtime_actions_mixin import MainWindowRuntimeActionsMixin
from main_window.singbox_mixin import SingBoxMainWindowMixin
from main_window.ui_shell_mixin import MainWindowUIShellMixin
from main_window.workers import EventThread, LocationsThread
class MainWindow(
MainWindowRuntimeActionsMixin,
SingBoxMainWindowMixin,
MainWindowUIShellMixin,
QMainWindow,
):
def __init__(self, controller: DashboardController) -> None:
super().__init__()
self.ctrl = controller
self.setWindowTitle("Selective VPN Dashboard (Qt)")
self.resize(1024, 700)
# login-flow state
self._login_flow_active: bool = False
self._login_cursor: int = 0
self._login_url_opened: bool = False
self.events_thread: EventThread | None = None
self.locations_thread: LocationsThread | None = None
self._locations_refresh_pending: bool = False
self._locations_force_refresh_pending: bool = False
self._vpn_desired_location: str = ""
self._vpn_desired_location_last_seen: str = ""
self._vpn_switching_active: bool = False
self._vpn_switching_target: str = ""
self._vpn_switching_started_at: float = 0.0
self._vpn_switching_seen_non_connected: bool = False
self._vpn_switching_min_visible_sec: float = 1.2
self._vpn_switching_timeout_sec: float = 15.0
self._loc_typeahead_buf: str = ""
self._all_locations: list[tuple[str, str, str, str, int]] = []
self._transport_clients = []
self._transport_policy_clients = []
self._transport_api_supported: bool = True
self._transport_kind: str = "singbox"
self._transport_health_live: dict[str, dict[str, Any]] = {}
self._transport_health_last_probe_ts: float = 0.0
self._egress_identity_cache: dict[str, Any] = {}
self._egress_identity_last_probe_ts: dict[str, float] = {}
self._vpn_egress_refresh_token: int = 0
self._vpn_autoloop_last_state: str = ""
self._vpn_autoloop_refresh_pending: bool = False
self._vpn_autoloop_last_force_refresh_ts: float = 0.0
self._syncing_singbox_selection: bool = False
self._singbox_editor_loading: bool = False
self._singbox_editor_profile_id: str = ""
self._singbox_editor_profile_client_id: str = ""
self._singbox_editor_protocol: str = "vless"
self._singbox_editor_source_raw: dict[str, Any] = {}
self._transport_policy_base_revision: int = 0
self._transport_policy_draft_intents: list[Any] = []
self._transport_policy_applied_intents: list[Any] = []
self._transport_policy_last_conflicts: list[Any] = []
self._transport_policy_dirty: bool = False
self._transport_policy_last_apply_id: str = ""
self._routes_progress_last: int = 0
self._dns_ui_refresh: bool = False
self._ui_settings = QSettings("AdGuardVPN", "SelectiveVPNDashboardQt")
self.login_poll_timer = QTimer(self)
self.login_poll_timer.setInterval(250)
self.login_poll_timer.timeout.connect(self._login_poll_tick)
self.dns_save_timer = QTimer(self)
self.dns_save_timer.setSingleShot(True)
self.dns_save_timer.setInterval(700)
self.dns_save_timer.timeout.connect(self._apply_dns_autosave)
self.loc_typeahead_timer = QTimer(self)
self.loc_typeahead_timer.setSingleShot(True)
self.loc_typeahead_timer.setInterval(900)
self.loc_typeahead_timer.timeout.connect(self._reset_location_typeahead)
self._build_ui()
self._load_ui_preferences()
self.refresh_everything()
self._start_events_stream()
# ---------------- UI BUILD ----------------
# UI shell/build + locations/egress helpers вынесены в main_window/ui_shell_mixin.py
# SingBox UI/actions/editor вынесены в main_window/singbox_mixin.py
# Runtime/refresh/actions вынесены в main_window/runtime_actions_mixin.py
def main(argv: list[str] | None = None) -> int:
if argv is None:
argv = sys.argv[1:]
base_url = "http://127.0.0.1:8080"
if argv:
base_url = argv[0]
client = ApiClient(base_url)
ctrl = DashboardController(client)
app = QApplication(sys.argv)
win = MainWindow(ctrl)
win.show()
return app.exec()
if __name__ == "__main__":
raise SystemExit(main())