All checks were successful
Windows project source guards / test (push) Successful in 1m30s
146 lines
5.3 KiB
Python
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})"
|
|
)
|