#!/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())