zapret-kvn/xray_fluent/ui/node_edit_page.py
loop-uh ab0b7897fc
All checks were successful
Windows project source guards / test (push) Successful in 1m30s
fix: preserve interactive server table and bound DNS fallback readiness
2026-09-06 02:07:48 +03:00

146 lines
5.3 KiB
Python

"""Server editing as an in-page breadcrumb sub-page (was NodeEditDialog)."""
from __future__ import annotations
from PyQt6.QtCore import pyqtSignal
from PyQt6.QtWidgets import QFormLayout, QWidget
from qfluentwidgets import (
BodyLabel,
CardWidget,
CaptionLabel,
EditableComboBox,
ComboBox,
FluentIcon as FIF,
LineEdit,
PrimaryPushButton,
PushButton,
StrongBodyLabel,
)
from ..profiles.models import Node
from .detail_page import DetailPage
from .nodes_table_model import node_type_text
from .privacy import HoldToRevealButton, endpoint_text
class NodeEditPage(DetailPage):
"""Edit one server's name, group and tags."""
save_requested = pyqtSignal(str, dict) # node_id, updated fields
def __init__(self, parent: QWidget | None = None):
super().__init__(
"Серверы",
"Редактирование сервера",
parent,
root_key="nodes",
page_key="node-edit",
)
self._node_id = ""
self._endpoint_server = ""
self._endpoint_port = 0
self._endpoint_scheme = "?"
self._endpoint_revealed = False
self._original: dict = {}
self.reveal_address_btn = HoldToRevealButton(self)
self.reveal_address_btn.revealChanged.connect(self._set_endpoint_revealed)
self.add_header_action(self.reveal_address_btn)
self.cancel_btn = PushButton("Отмена", self)
self.cancel_btn.clicked.connect(self.request_back)
self.add_header_action(self.cancel_btn)
self.save_btn = PrimaryPushButton(FIF.SAVE, "Сохранить", self)
self.save_btn.clicked.connect(self._emit_save)
self.add_header_action(self.save_btn)
card = CardWidget(self.body)
form = QFormLayout(card)
form.setContentsMargins(20, 18, 20, 18)
form.setSpacing(10)
self.endpoint_label = StrongBodyLabel("", card)
form.addRow(BodyLabel("Адрес", card), self.endpoint_label)
self.name_edit = LineEdit(card)
self.name_edit.setPlaceholderText("Имя сервера")
self.name_edit.returnPressed.connect(self._emit_save)
form.addRow(BodyLabel("Название", card), self.name_edit)
self.group_combo = EditableComboBox(card)
form.addRow(BodyLabel("Группа", card), self.group_combo)
from ..profiles.country_flags import _VALID_CODES
self.country_combo = ComboBox(card)
self.country_combo.addItem("Автоматически — флаг в имени или GeoIP", userData="")
for code in sorted(_VALID_CODES):
self.country_combo.addItem(code, userData=code)
form.addRow(BodyLabel("Страна", card), self.country_combo)
self.tags_edit = LineEdit(card)
self.tags_edit.setPlaceholderText("tag1, tag2, tag3")
form.addRow(BodyLabel("Теги", card), self.tags_edit)
form.addRow("", CaptionLabel("Перечислите теги через запятую", card))
self.content_layout.addWidget(card)
self.content_layout.addStretch(1)
# ── Public API ──
def set_node(self, node: Node, existing_groups: list[str]) -> None:
self.reveal_address_btn.reset_reveal()
self._endpoint_revealed = False
self._node_id = node.id
self._endpoint_server = node.server
self._endpoint_port = node.port
self._endpoint_scheme = node_type_text(node)
self._refresh_endpoint()
self.group_combo.clear()
for group in existing_groups:
self.group_combo.addItem(group)
for index in range(self.country_combo.count()):
if self.country_combo.itemData(index) == node.country_override:
self.country_combo.setCurrentIndex(index)
break
else:
self.country_combo.setCurrentIndex(0)
self.name_edit.setText(node.name)
self.group_combo.setText(node.group)
self.tags_edit.setText(", ".join(node.tags))
self.set_page_label(node.name or "Без имени")
self._original = self.get_updated_fields()
def get_updated_fields(self) -> dict:
raw_tags = self.tags_edit.text().strip()
tags = [t.strip() for t in raw_tags.split(",") if t.strip()] if raw_tags else []
return {
"name": self.name_edit.text().strip(),
"country_override": self.country_combo.currentData() or "",
"group": self.group_combo.text().strip() or "Default",
"tags": tags,
}
def mark_clean(self) -> None:
self._original = self.get_updated_fields()
def is_dirty(self) -> bool:
return bool(self._original) and self.get_updated_fields() != self._original
# ── Internals ──
def _emit_save(self) -> None:
if not self._node_id:
return
self.save_requested.emit(self._node_id, self.get_updated_fields())
def _set_endpoint_revealed(self, revealed: bool) -> None:
self._endpoint_revealed = bool(revealed)
self._refresh_endpoint()
def _refresh_endpoint(self) -> None:
self.endpoint_label.setText(
f"{endpoint_text(self._endpoint_server, self._endpoint_port, self._endpoint_revealed)} "
f"({self._endpoint_scheme})"
)