feat(lite): 持久化代理模式配置并发布 9999091816
This commit is contained in:
+178
-4
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
|
||||
QComboBox, QDialog, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
|
||||
QPushButton, QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
@@ -292,6 +292,180 @@ class WhitelistDialog(QDialog):
|
||||
self.accept()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FixedRouteServices:
|
||||
"""域名路由编辑框所需的规则与节点服务。"""
|
||||
|
||||
load_icon: object
|
||||
load_rules: object
|
||||
available_nodes: object
|
||||
validate_domain: object
|
||||
save_rules: object
|
||||
|
||||
|
||||
class FixedRouteDialog(QDialog):
|
||||
"""为域名配置强制直连或指定节点代理。"""
|
||||
|
||||
def __init__(self, services: FixedRouteServices, parent=None):
|
||||
super().__init__(parent)
|
||||
self._services = services
|
||||
self.setWindowTitle("域名路由")
|
||||
self.setWindowIcon(services.load_icon())
|
||||
self.setFixedSize(620, 530)
|
||||
self.setStyleSheet(f"""
|
||||
QDialog {{ background-color: {C_BG}; }}
|
||||
QLabel {{ color: {C_TEXT}; font-family: "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 12px; }}
|
||||
QLineEdit, QComboBox {{ background-color: {C_CARD}; border: 1px solid {C_BORDER}; border-radius: 6px;
|
||||
padding: 5px 8px; color: {C_TEXT}; font-size: 12px; }}
|
||||
QListWidget {{ background-color: {C_CARD}; border: 1px solid {C_BORDER}; border-radius: 6px;
|
||||
padding: 4px; color: {C_TEXT}; font-size: 12px; }}
|
||||
QListWidget::item {{ padding: 5px 7px; border-radius: 4px; }}
|
||||
QListWidget::item:selected {{ background-color: #e3e8ee; color: {C_TEXT}; }}
|
||||
QPushButton#btnOk {{ background-color: {C_GREEN}; color: #fff; border: none;
|
||||
border-radius: 6px; padding: 5px 12px; font-size: 12px; font-weight: bold; }}
|
||||
QPushButton#btnOk:hover {{ background-color: #5ed978; }}
|
||||
QPushButton#btnSmall {{ background-color: {C_CARD}; color: {C_SUBTEXT}; border: 1px solid {C_BORDER};
|
||||
border-radius: 6px; padding: 4px 10px; font-size: 11px; }}
|
||||
QPushButton#btnSmall:hover {{ background-color: #e3e8ee; color: {C_TEXT}; }}
|
||||
""")
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(9)
|
||||
layout.setContentsMargins(18, 14, 18, 14)
|
||||
|
||||
title = QLabel("域名路由")
|
||||
title.setStyleSheet(f"color: {C_TITLE}; font-size: 15px; font-weight: bold;")
|
||||
title.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(title)
|
||||
hint = QLabel("example.com 匹配根域及子域;*.example.com 仅匹配子域")
|
||||
hint.setStyleSheet(f"color: {C_SUBTEXT}; font-size: 11px;")
|
||||
hint.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(hint)
|
||||
|
||||
self.lb_err = QLabel("")
|
||||
self.lb_err.setStyleSheet(f"color: {C_RED}; font-size: 11px;")
|
||||
self.lb_err.setFixedHeight(16)
|
||||
self.lb_err.hide()
|
||||
layout.addWidget(self.lb_err)
|
||||
|
||||
self.list_widget = QListWidget()
|
||||
self.list_widget.setToolTip("勾选启用规则;选中后可删除")
|
||||
layout.addWidget(self.list_widget, 1)
|
||||
|
||||
domain_row = QHBoxLayout()
|
||||
domain_row.setSpacing(7)
|
||||
self.ed_domain = QLineEdit()
|
||||
self.ed_domain.setPlaceholderText("域名,如 example.com 或 *.example.com")
|
||||
self.ed_domain.returnPressed.connect(self._add_rule)
|
||||
domain_row.addWidget(self.ed_domain, 1)
|
||||
self.cb_action = QComboBox()
|
||||
self.cb_action.addItem("强制不代理", "direct")
|
||||
self.cb_action.addItem("强制节点代理", "node")
|
||||
self.cb_action.currentIndexChanged.connect(self._on_action_changed)
|
||||
domain_row.addWidget(self.cb_action)
|
||||
self.cb_node = QComboBox()
|
||||
self.cb_node.setMinimumWidth(190)
|
||||
domain_row.addWidget(self.cb_node)
|
||||
self.btn_add = QPushButton("添加")
|
||||
self.btn_add.setObjectName("btnOk")
|
||||
self.btn_add.clicked.connect(self._add_rule)
|
||||
domain_row.addWidget(self.btn_add)
|
||||
layout.addLayout(domain_row)
|
||||
|
||||
middle = QHBoxLayout()
|
||||
self.btn_delete = QPushButton("删除选中")
|
||||
self.btn_delete.setObjectName("btnSmall")
|
||||
self.btn_delete.clicked.connect(self._delete_selected)
|
||||
middle.addWidget(self.btn_delete)
|
||||
middle.addStretch()
|
||||
layout.addLayout(middle)
|
||||
|
||||
buttons = QHBoxLayout()
|
||||
buttons.addStretch()
|
||||
self.btn_cancel = QPushButton("取消")
|
||||
self.btn_cancel.setObjectName("btnSmall")
|
||||
self.btn_cancel.clicked.connect(self.reject)
|
||||
buttons.addWidget(self.btn_cancel)
|
||||
self.btn_save = QPushButton("保存")
|
||||
self.btn_save.setObjectName("btnOk")
|
||||
self.btn_save.clicked.connect(self._save)
|
||||
buttons.addWidget(self.btn_save)
|
||||
layout.addLayout(buttons)
|
||||
|
||||
self._reload_nodes()
|
||||
for rule in services.load_rules():
|
||||
self._add_item(rule)
|
||||
self._on_action_changed()
|
||||
|
||||
def _reload_nodes(self):
|
||||
self.cb_node.clear()
|
||||
for node in self._services.available_nodes():
|
||||
self.cb_node.addItem(node["label"], node["ref"])
|
||||
if not self.cb_node.count():
|
||||
self.cb_node.addItem("暂无可用节点", None)
|
||||
|
||||
def _on_action_changed(self, *_):
|
||||
self.cb_node.setEnabled(self.cb_action.currentData() == "node")
|
||||
|
||||
def _add_item(self, rule):
|
||||
domain = str(rule.get("domain") or rule.get("value") or "").strip()
|
||||
if not domain:
|
||||
return
|
||||
action = str(rule.get("action") or "direct")
|
||||
node_label = str(rule.get("node_label") or "节点不可用")
|
||||
target = "强制不代理" if action == "direct" else f"节点代理:{node_label}"
|
||||
item = QListWidgetItem(f"{domain} → {target}")
|
||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
||||
item.setCheckState(Qt.Checked if rule.get("enabled", True) else Qt.Unchecked)
|
||||
item.setData(Qt.UserRole, {
|
||||
"domain": domain,
|
||||
"action": action,
|
||||
"node_ref": str(rule.get("node_ref") or ""),
|
||||
})
|
||||
self.list_widget.addItem(item)
|
||||
|
||||
def _add_rule(self):
|
||||
domain, error = self._services.validate_domain(self.ed_domain.text())
|
||||
if error:
|
||||
self.lb_err.setText(error)
|
||||
self.lb_err.show()
|
||||
return
|
||||
action = self.cb_action.currentData()
|
||||
node_ref = self.cb_node.currentData() if action == "node" else ""
|
||||
if action == "node" and not node_ref:
|
||||
self.lb_err.setText("请先配置至少一个可用节点")
|
||||
self.lb_err.show()
|
||||
return
|
||||
for index in range(self.list_widget.count()):
|
||||
existing = self.list_widget.item(index).data(Qt.UserRole) or {}
|
||||
if str(existing.get("domain") or "").lower() == domain.lower():
|
||||
self.lb_err.setText("该域名规则已存在")
|
||||
self.lb_err.show()
|
||||
return
|
||||
self.lb_err.hide()
|
||||
self._add_item({
|
||||
"domain": domain,
|
||||
"action": action,
|
||||
"node_ref": node_ref,
|
||||
"node_label": self.cb_node.currentText() if action == "node" else "",
|
||||
"enabled": True,
|
||||
})
|
||||
self.ed_domain.clear()
|
||||
|
||||
def _delete_selected(self):
|
||||
for item in self.list_widget.selectedItems():
|
||||
self.list_widget.takeItem(self.list_widget.row(item))
|
||||
|
||||
def _save(self):
|
||||
rules = []
|
||||
for index in range(self.list_widget.count()):
|
||||
item = self.list_widget.item(index)
|
||||
rule = dict(item.data(Qt.UserRole) or {})
|
||||
rule["enabled"] = item.checkState() == Qt.Checked
|
||||
rules.append(rule)
|
||||
self._services.save_rules(rules)
|
||||
self.accept()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class JumpServices:
|
||||
"""代理跳板管理所需的节点摘要与配置回调。"""
|
||||
@@ -682,7 +856,7 @@ class SettingsServices:
|
||||
|
||||
|
||||
class SettingsDialog(QDialog):
|
||||
"""代理端口、更新方式、白名单与登录状态设置框。"""
|
||||
"""代理端口、更新方式、域名路由与登录状态设置框。"""
|
||||
|
||||
def __init__(self, services: SettingsServices, parent=None, port_locked=False):
|
||||
super().__init__(parent)
|
||||
@@ -728,9 +902,9 @@ class SettingsDialog(QDialog):
|
||||
layout.addStretch()
|
||||
|
||||
whitelist_row = QHBoxLayout()
|
||||
whitelist_label = QLabel("代理白名单")
|
||||
whitelist_label = QLabel("域名路由")
|
||||
whitelist_label.setStyleSheet(f"color: {C_SUBTEXT}; font-size: 12px;")
|
||||
whitelist_label.setToolTip("命中列表的域名 / IP 将强制直连(不经过代理),每条规则可单独启用")
|
||||
whitelist_label.setToolTip("为域名配置强制不代理,或强制经过指定节点;支持 *.example.com")
|
||||
whitelist_row.addWidget(whitelist_label)
|
||||
whitelist_row.addStretch()
|
||||
self.btn_whitelist = QPushButton("配置")
|
||||
|
||||
Reference in New Issue
Block a user