zapret-kvn/xray_fluent/ui/preset_edit_widget.py
loop-uh f9971d5a1f
Some checks failed
Windows project source guards / test (push) Failing after 31s
Zapret: модуль и страница переписаны — проще, понятнее, строже
Движок разложен на части с одной задачей каждая:
- endpoint: вид сервера (TCP / QUIC / WireGuard), хосты, порты, DNS;
- strategies: каталоги winws2 и правило для выбранного сервера;
- presets: файлы пресетов;
- command: сборка командной строки winws2 (чистая функция);
- manager: процесс winws2 с одним входом apply(план) и ревизиями —
  готовность только по started процесса нужной ревизии, таймаут 5 с.
Склейка с подключением (~450 строк в контроллере, два наслоенных
механизма «UDP-защиты» и «профиля сервера», кэш и прогрев DNS) заменена
шлюзом application/server_bypass.py. Сохранены: DNS только в воркере,
остановка туннеля перед сменой правила (кроме восстановления Hysteria),
забор перед стартом ядра, fail-closed при потере winws2 (теперь и по
таймауту перезапуска), маскировка IP сервера в логах до старта ядра;
адреса серверов пула изучаются в фоне после подключения.

Поведение:
- у QUIC и WireGuard теперь свои стратегии; ключ zapret_target совместим,
  старые сборки после отката читают udp_* как раньше;
- в каталогах вернулись 82 составные стратегии с ветками --payload
  (валидатор принимал только --lua-desync и молча их выбрасывал);
- смена пресета под защищённой сессией идёт через переход: туннель
  останавливается, winws2 перезапускается, VPN подключается снова;
- пропавший файл выбранного пресета — откат к Default, а не отказ;
- правка работающего пресета перезапускает его; переименование больше
  не оставляет копию, занятое имя отклоняется;
- настройки Zapret не откатываются при сохранении страницы «Настройки».

Интерфейс: карточка состояния (работает/нет, пресет, одна кнопка,
автозапуск), выбранный VPN-сервер с живым правилом, три строки видов
серверов с подстраницей стратегии, пресеты — отдельной подстраницей.
Объяснение — в окне «Как это б#&^ь работает?» с иллюстрацией.
2026-09-28 20:53:25 +03:00

122 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from PyQt6.QtCore import pyqtSignal
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget
from qfluentwidgets import (
BodyLabel,
CaptionLabel,
CardWidget,
FluentIcon as FIF,
LineEdit,
PlainTextEdit,
PrimaryPushButton,
)
from .detail_page import DetailPage
class PresetEditWidget(DetailPage):
#: original name ("" for a new preset), name, description, content
save_requested = pyqtSignal(str, str, str, str)
def __init__(self, parent: QWidget | None = None):
super().__init__(
"Zapret",
"Редактирование пресета",
parent,
root_key="zapret",
page_key="preset-edit",
)
self._original_content = ""
self._original_name = ""
self._original_description = ""
root = self.content_layout
self.error_label = CaptionLabel("", self)
self.add_header_action(self.error_label)
self.save_btn = PrimaryPushButton(FIF.SAVE, "Сохранить", self)
self.save_btn.clicked.connect(self._on_save)
self.add_header_action(self.save_btn)
# Metadata card
meta_card = CardWidget(self)
meta_layout = QVBoxLayout(meta_card)
meta_layout.setContentsMargins(16, 12, 16, 12)
meta_layout.setSpacing(8)
name_row = QHBoxLayout()
name_row.addWidget(BodyLabel("Название:", meta_card))
self.name_edit = LineEdit(meta_card)
self.name_edit.setPlaceholderText("Имя пресета")
name_row.addWidget(self.name_edit, 1)
meta_layout.addLayout(name_row)
desc_row = QHBoxLayout()
desc_row.addWidget(BodyLabel("Описание:", meta_card))
self.desc_edit = LineEdit(meta_card)
self.desc_edit.setPlaceholderText("Краткое описание (необязательно)")
desc_row.addWidget(self.desc_edit, 1)
meta_layout.addLayout(desc_row)
self.meta_label = CaptionLabel("", meta_card)
meta_layout.addWidget(self.meta_label)
root.addWidget(meta_card)
# Content editor
self.editor = PlainTextEdit(self)
self.editor.setPlaceholderText("Аргументы winws2, по одному на строку.\nСтроки с # — комментарии.")
font = QFont("Consolas", 10)
font.setStyleHint(QFont.StyleHint.Monospace)
self.editor.setFont(font)
root.addWidget(self.editor, 1)
def set_preset(self, name: str, description: str, content: str,
created: str = "", modified: str = "") -> None:
"""Load a preset into the editor."""
self._original_name = name
self._original_description = description
self._original_content = content
self.error_label.setText("")
self.name_edit.setText(name)
self.desc_edit.setText(description)
self.editor.setPlainText(content)
self.set_page_label(name or "Новый пресет")
# Meta info
parts = []
if created:
parts.append(f"Создан: {created}")
if modified:
parts.append(f"Изменён: {modified}")
self.meta_label.setText(" | ".join(parts) if parts else "")
def is_dirty(self) -> bool:
"""Check if content was modified."""
return (self.editor.toPlainText() != self._original_content
or self.name_edit.text() != self._original_name
or self.desc_edit.text() != self._original_description)
def _on_save(self) -> None:
name = self.name_edit.text().strip()
if not name:
self.error_label.setText("Введите название")
return
if any(char in '\\/:*?"<>|' for char in name) or name.startswith("_"):
self.error_label.setText("Название не может содержать \\ / : * ? \" < > | и начинаться с _")
return
self.error_label.setText("")
desc = self.desc_edit.text().strip()
content = self.editor.toPlainText()
self.save_requested.emit(self._original_name, name, desc, content)
def mark_saved(self, name: str, description: str, content: str) -> None:
"""The file is written: this is the new clean state."""
self._original_name = name
self._original_description = description
self._original_content = content
def show_error(self, message: str) -> None:
self.error_label.setText(message)