124 lines
4.6 KiB
Python
Executable File
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())
|