# F1 План рефакторинга и модульности Дата: 2026-03-07 Статус: planned Владелец: Engineering ## 1) Цель - Уменьшить размер и связность крупных файлов без изменения поведения приложения. - Подготовить кодовую базу к быстрому развитию desktop/web/mobile поверх одного Go API. ## 2) Текущие "горячие" файлы - `selective-vpn-gui/vpn_dashboard_qt.py` — `6103` строк (до старта F1.4; после текущего разреза: `116` + `main_window/ui_shell_mixin.py` + `main_window/singbox/*` + `main_window/runtime_actions_mixin.py`). - `selective-vpn-gui/api_client.py` — `2220` строк. - `selective-vpn-gui/dashboard_controller.py` — `1430` строк. - `selective-vpn-api/app/transport_handlers.go` — `1984` строки. - `selective-vpn-api/app/server.go` (legacy) — центральная связка entrypoints/bootstrap/routes, высокий риск регрессий при точечных изменениях. ## 3) Принципы разрезания - Поведение не меняем: сначала вынос кода, потом точечные улучшения. - Модули режем по domain-границам, а не "по 500 строк". - Сохраняем один публичный entrypoint (facade) для UI и для backend-router. - После каждого шага прогоняем существующие smoke/e2e (без новых требований к тестам от пользователя). ## 4) План разрезания GUI ### 4.1 `vpn_dashboard_qt.py` -> `gui/main_window/*` - Цель: вынести табы и event-обвязку в отдельные модули. - Разделение: - `main_window.py` — сборка окна и общий lifecycle. - `tabs/status_tab.py` - `tabs/vpn_tab.py` - `tabs/routes_tab.py` - `tabs/dns_tab.py` - `tabs/domains_tab.py` - `tabs/trace_tab.py` - `services/events_bridge.py` (`EventThread`, dispatch) - `services/locations_loader.py` (`LocationsThread`, SWR-ui orchestration) - Критерий: исходный файл `vpn_dashboard_qt.py` становится thin-bootstrap. - Статус: выполнено (`2026-03-10`). - Уже сделано: - добавлены `main_window/constants.py` и `main_window/workers.py` (shared constants + `EventThread`/`LocationsThread`); - вынесен UI shell (`build tabs + helpers + locations/egress`) в `main_window/ui_shell_mixin.py`; - `ui_tabs_singbox_mixin.py` дополнительно разрезан на `ui_tabs_singbox_layout_mixin.py` и `ui_tabs_singbox_editor_mixin.py`; - SingBox-контур дополнительно разрезан на подпакет `main_window/singbox/*` (`editor`, `cards`, `links`, `runtime`) с фасадом `main_window/singbox_mixin.py`; - вынесен runtime/refresh/actions контур в `main_window/runtime_actions_mixin.py`; - проведён второй проход декомпозиции: `ui_tabs_*`, `runtime_{state,refresh,auth,ops}`, `singbox/{links_*,runtime_*}`; фасады оставлены для совместимости; - `MainWindow` переведён на наследование mixin-классов, поведение сохранено; в итоге крупные GUI-модули уложены в читаемые размеры (максимум `~633` строк). ### 4.2 `dashboard_controller.py` -> `controllers/*` - Разделение: - `controllers/status_controller.py` - `controllers/vpn_controller.py` - `controllers/routes_controller.py` - `controllers/dns_controller.py` - `controllers/domains_controller.py` - `controllers/transport_controller.py` - `controllers/trace_controller.py` - Сохраняется фасад `DashboardController` (совместимость с текущим UI-кодом). - Статус: выполнено (`2026-03-10`). - Что сделано: - добавлен пакет `selective-vpn-gui/controllers/` (`core + views + domain mixin-модули`); - `selective-vpn-gui/dashboard_controller.py` переведён в thin-facade с прежней точкой входа; - `py_compile` и импорты UI-модулей (`vpn_dashboard_qt.py`, `traffic_mode_dialog.py`, `dns_benchmark_dialog.py`) проходят. ### 4.3 `api_client.py` -> `api/*` - Разделение: - `api/client.py` (HTTP/SSE base + shared helpers) - `api/models.py` (dataclasses) - `api/status.py`, `api/vpn.py`, `api/routes.py`, `api/traffic.py`, `api/dns.py`, `api/domains.py`, `api/trace.py` - `api/transport.py` facade + `api/transport_clients.py`, `api/transport_policy.py`, `api/transport_singbox.py` - Сохраняется фасад `ApiClient` (старая точка импорта для UI). - Статус: выполнено (`2026-03-10`). - Что сделано: - добавлен пакет `selective-vpn-gui/api/` с доменными mixin-модулями; - `api_client.py` переведён в backward-compatible facade (legacy imports сохранены); - `api/client.py` сокращён до base-слоя, доменные методы вынесены по подпапкам; - `api/transport.py` разрезан на subdomain-модули с сохранением класса `TransportApiMixin`. ## 5) План разрезания Go-ядра ### 5.1 `transport_handlers.go` -> `app/transport_*` - Разделение: - `transport_handlers_clients.go` (CRUD/lifecycle/health/metrics) - `transport_handlers_policy.go` (validate/apply/rollback/conflicts) - `transport_validator.go` (normalize + conflict detection) - `transport_confirm_token.go` (token lifecycle) - `transport_state_store.go` (load/save state/snapshots) - Сохраняем текущие endpoint-path и response-контракт без изменений. ### 5.2 `server.go` -> `app/entrypoints.go + api_bootstrap.go + api_routes.go` - Цель: развязать entrypoint-логику (`Run*CLI`), bootstrap API-сервера и registry маршрутов. - Статус: выполнено (`2026-03-10`). - Состав: - `entrypoints.go` — `Run`, `RunAPIServer`, `RunRoutesUpdateCLI`, `RunRoutesClearCLI`, `RunAutoloopCLI`, legacy-dispatch; - `api_bootstrap.go` — bootstrap и lifecycle HTTP-сервера (`runAPIServerAtAddr`); - `api_routes.go` — thin-facade `registerAPIRoutes`; - `api_routes_*.go` — доменные registrars (`core`, `traffic`, `transport`, `trace`, `dns`, `vpn`) без изменения endpoint-path. ### 5.3 `app/*` -> подпакеты runtime (`app/cli`, `app/bootstrap`) - Цель: убрать "кучу" в корне `app` и перенести исполнимые runner-модули в отдельные папки, сохранив фасады в `app`. - Статус: выполнено (`2026-03-10`). - Состав: - `app/cli/*` — `routes-update`, `routes-clear`, `autoloop` раннеры с dependency-injection (callback deps); - `app/bootstrap/server_runner.go` — единый HTTP server runner; - `app/entrypoints.go` и `app/api_bootstrap.go` оставлены thin-facade слоями. ### 5.4 `transport_handlers.go` -> модульные transport файлы - Цель: убрать крупнейший монолит transport control-plane без изменения endpoint-контракта. - Статус: выполнено (`2026-03-10`). - Состав: - `transport_shared.go` — state/version constants, mutex, общие типы; - `transport_handlers_clients.go` — clients CRUD/lifecycle/netns toggle handlers; - `transport_handlers_policy.go` — policy validate/apply/rollback/conflicts/capabilities handlers; - `transport_policy_validate.go` — normalize/validate/diff/conflict helpers; - `transport_client_runtime.go` — runtime/health/lifecycle/allocation helpers; - `transport_tokens_state.go` — confirm-token и state persistence. ### 5.5 Подпакеты transport (без циклических зависимостей) - Цель: выносить переиспользуемые части transport-логики в подпапки только там, где не возникает import-cycle с `app`. - Статус: in_progress (`2026-03-10`). - Уже вынесено: - `app/transporttoken/store.go` — confirm-token store (`issue/consume/ttl cleanup`) и token generator. - Дальше: - выделить в подпакеты часть stateless helper-функций (при сохранении фасадов в `app`). ## 6) Порядок выполнения (безопасный) 1. `api_client.py` (минимальный риск для UI, проще ревьюить). 2. `dashboard_controller.py`. 3. `vpn_dashboard_qt.py` (таб за табом). 4. `transport_handlers.go` в Go. ## 7) Definition of Done - Все текущие smoke/e2e проходят: - `./tests/run_all.sh` - плюс ручная проверка пользователем в desktop приложении. - Нет изменений API-контракта (`/api/v1/*`) и event-kind. - В каждом крупном модуле остаётся читаемый размер (ориентир `< 700` строк; предпочтительно `< 500`). ## 8) Дополнительно (после F1) - Добавить settings-механику видимости вкладок протоколов (`SingBox`, `DNSTT`, `Phoenix`) через `QSettings`/profile, чтобы включать только нужные модули UI.