基于ChmlFrp开发的ui客户端的部分源码

import ast
import logging
import os
import shutil
import subprocess
import sys
import time
import zipfile
from logging.handlers import RotatingFileHandler
import requests
from PyQt6.QtCore import *
from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
import socket
import threading
import json
from mcstatus import JavaServer, BedrockServer
from requests import RequestException

# 设置全局日志
logger = logging.getLogger('dynamic_tunneling')
logger.setLevel(logging.DEBUG)
file_handler = RotatingFileHandler('dynamic_tunneling.log', maxBytes=5 * 1024 * 1024, backupCount=5)
file_handler.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)



def get_pos(event):
    """获取事件的全局位置"""
    if hasattr(event, 'globalPos'):
        return event.globalPos()
    else:
        return event.globalPosition().toPoint()

def get_config_folder():
    base_path = os.path.dirname(os.path.abspath(__file__))
    config_folder = os.path.join(base_path, "config")
    if not os.path.exists(config_folder):
        os.makedirs(config_folder)
    return config_folder


def get_nodes(max_retries=3, retry_delay=1):
    """获取节点数据"""
    logger.info("开始获取节点数据")
    url = "http://cf-v2.uapis.cn/node"
    headers = {'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'}

    for attempt in range(max_retries):
        try:
            response = requests.post(url, headers=headers)
            response.raise_for_status()  # 如果响应状态不是200,将引发HTTPError异常
            data = response.json()
            if data['code'] == 200:
                return data['data']
            else:
                logger.error(f"获取节点数据失败: {data['msg']}")
                return []
        except RequestException as e:
            logger.warning(f"获取节点数据时发生网络错误 (尝试 {attempt + 1}/{max_retries}): {str(e)}")
            if attempt < max_retries - 1:
                time.sleep(retry_delay)
            else:
                logger.error("获取节点数据失败,已达到最大重试次数")
                return []
        except Exception as e:
            logger.exception("获取节点数据时发生未知错误")
            return []

def login(username, password):
    """用户登录返回token"""
    logger.info(f"尝试登录用户: {username}")
    url = f"http://cf-v2.uapis.cn/login?username={username}&password={password}"
    headers = {'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'}
    try:
        response = requests.get(url, headers=headers)
        response_data = response.json()
        token = response_data.get("data", {}).get("usertoken")
        if token:
            logger.info("登录成功")
        else:
            logger.warning("登录失败")
        return token
    except Exception as e:
        logger.exception("登录时发生错误")
        logger.exception(e)
        return None

def get_user_tunnels(token):
    """获取用户隧道列表"""
    url = f"http://cf-v2.uapis.cn/tunnel?token={token}"
    headers = {'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'}
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()  # 这会在HTTP错误时抛出异常
        data = response.json()
        if data['code'] == 200:
            tunnels = data.get("data", [])
            return tunnels
        else:
            logger.error(f"获取隧道列表失败: {data.get('msg', '未知错误')}")
            return None
    except requests.RequestException as e:
        logger.exception("获取用户隧道列表时发生网络错误")
        return None
    except Exception as e:
        logger.exception("获取用户隧道列表时发生未知错误")
        return None

def get_node_ip(token, node):
    """获取节点IP"""
    logger.info(f"获取节点 {node} 的IP")
    url = f"http://cf-v2.uapis.cn/nodeinfo?token={token}&node={node}"
    headers = {'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'}
    try:
        response = requests.get(url, headers=headers)
        ip = response.json()["data"]["realIp"]
        logger.info(f"节点 {node} 的IP为 {ip}")
        return ip
    except Exception as e:
        logger.exception(f"获取节点 {node} 的IP时发生错误")
        logger.exception(e)
        return None

def update_subdomain(token, domain, record, target, record_type):
    """更新子域名"""
    logger.info(f"更新子域名 {record}.{domain} 到 {target}")
    url = "http://cf-v2.uapis.cn/update_free_subdomain"
    payload = {
        "token": token,
        "domain": domain,
        "record": record,
        "type": record_type,
        "target": target,
        "ttl": "1分钟",
        "remarks": ""
    }
    headers = {
        'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
        'Content-Type': 'application/json'
    }
    try:
        response = requests.post(url, headers=headers, json=payload)
        if response.status_code == 200:
            logger.info("子域名更新成功")
        else:
            logger.warning(f"子域名更新失败: {response.text}")
    except Exception as e:
        logger.exception("更新子域名时发生错误")
        logger.exception(e)

def get_tunnel_info(tunnels, tunnel_name):
    """获取特定隧道信息"""
    logger.info(f"获取隧道 {tunnel_name} 的信息")
    for tunnel in tunnels:
        if tunnel.get("name") == tunnel_name:
            logger.debug(f"找到隧道 {tunnel_name} 的信息")
            return tunnel
    logger.warning(f"未找到隧道 {tunnel_name} 的信息")
    return None

def update_tunnel(token, tunnel_info, node):
    """更新隧道信息"""
    logger.info(f"更新隧道 {tunnel_info['name']} 到节点 {node}")
    url = "http://cf-v2.uapis.cn/update_tunnel"
    payload = {
        "tunnelid": int(tunnel_info["id"]),
        "token": token,
        "tunnelname": tunnel_info["name"],
        "node": str(node),
        "localip": tunnel_info["localip"],
        "porttype": tunnel_info["type"],
        "localport": tunnel_info["nport"],
        "remoteport": tunnel_info["dorp"],
        "banddomain": "",
        "encryption": tunnel_info["encryption"],
        "compression": tunnel_info["compression"],
        "extraparams": ""
    }
    headers = {
        'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
        'Content-Type': 'application/json'
    }
    try:
        response = requests.post(url, headers=headers, json=payload)
        if response.status_code == 200:
            logger.info("隧道更新成功")
        else:
            logger.warning(f"隧道更新失败: {response.text}")
    except Exception as e:
        logger.exception("更新隧道时发生错误")
        logger.exception(e)

def get_tunnel_config(token, node, tunnel_name):
    """获取隧道配置"""
    url = f"http://cf-v2.uapis.cn/tunnel_config?token={token}&node={node}&tunnel_names={tunnel_name}"
    headers = {'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'}
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()  # 这会在HTTP错误时抛出异常
        data = response.json()
        if data['code'] == 200:
            config = data.get("data")
            if config:
                return config
            else:
                logger.warning(f"获取隧道配置失败: 返回数据中没有 'data' 字段")
        else:
            logger.warning(f"获取隧道配置失败: {data.get('msg', '未知错误')}")
        return None
    except requests.RequestException as e:
        logger.exception(f"获取隧道配置时发生网络错误: {str(e)}")
        return None
    except Exception as e:
        logger.exception(f"获取隧道配置时发生未知错误: {str(e)}")
        return None

def is_node_online(node_name):
    """检查节点是否在线"""
    url = "http://cf-v2.uapis.cn/node_stats"
    headers = {'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'}
    try:
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            stats = response.json()
            if stats and 'data' in stats:
                for node in stats['data']:
                    if node['node_name'] == node_name:
                        return node['state'] == "online"
        return False
    except Exception as e:
        logger.exception("检查节点在线状态时发生错误")
        logger.exception(e)
        return False

def read_config():
    file_path = get_absolute_path("Dynamic_tunneling.json")
    try:
        with open(file_path, 'r') as file:
            content = file.read()
            if content.strip():
                return json.loads(content)
            else:
                return []
    except FileNotFoundError:
        return []
    except json.JSONDecodeError as e:
        print(f"JSON解析错误: {e}")
        return []


def get_absolute_path(relative_path):
    """获取文件的绝对路径"""
    if relative_path == "dynamic_tunneling.log":
        base_path = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(base_path, relative_path)
    else:
        return os.path.join(get_config_folder(), relative_path)


def write_config(configs):
    file_path = get_absolute_path("Dynamic_tunneling.json")
    with open(file_path, 'w') as file:
        json.dump(configs, file, indent=2)

def parse_domain(domain):
    """解析域名"""
    logger.info(f"解析域名: {domain}")
    parts = domain.split('.')
    if len(parts) >= 3:
        subdomain = '.'.join(parts[:-2])
        main_domain = '.'.join(parts[-2:])
    else:
        subdomain = parts[0]
        main_domain = '.'.join(parts[-2:])
    logger.debug(f"解析结果 - 子域名: {subdomain}, 主域名: {main_domain}")
    return subdomain, main_domain

class QtHandler(QObject, logging.Handler):
    """Qt日志处理器"""
    new_record = pyqtSignal(str)

    def __init__(self, parent):
        super().__init__(parent)
        super(logging.Handler).__init__()
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        self.setFormatter(formatter)

    def emit(self, record):
        msg = self.format(record)
        self.new_record.emit(msg)

def setup_logging(parent):
    """设置日志系统"""
    logger = logging.getLogger('dynamic_tunneling')
    logger.setLevel(logging.DEBUG)

    # 文件处理器
    file_handler = RotatingFileHandler('dynamic_tunneling.log', maxBytes=5 * 1024 * 1024, backupCount=5)
    file_handler.setLevel(logging.DEBUG)
    file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    file_handler.setFormatter(file_formatter)
    logger.addHandler(file_handler)

    # Qt处理器
    qt_handler = QtHandler(parent)
    qt_handler.setLevel(logging.INFO)
    logger.addHandler(qt_handler)

    return logger, qt_handler


class CustomListItem(QListWidgetItem):
    def __init__(self, widget):
        super().__init__()
        self.widget = widget
        self.setSizeHint(widget.sizeHint())

# 添加 CustomItemDelegate 类
class CustomItemDelegate(QStyledItemDelegate):
    def paint(self, painter: QPainter, option, index):
        try:
            if option.state & QStyle.StateFlag.State_Selected:
                painter.fillRect(option.rect, option.palette.highlight())
            widget = index.data(Qt.ItemDataRole.UserRole)
            if widget:
                painter.save()
                painter.translate(option.rect.topLeft())
                widget.render(painter, QPoint(), QRegion(), QWidget.RenderFlag.DrawChildren)
                painter.restore()
        except Exception as e:
            print(f"Error in CustomItemDelegate.paint: {str(e)}")

    def sizeHint(self, option, index):
        widget = index.data(Qt.ItemDataRole.UserRole)
        if widget:
            return widget.sizeHint()
        return super().sizeHint(option, index)

class PingThread(QThread):
    """Ping线程"""
    update_signal = pyqtSignal(str, object)

    def __init__(self, target, ping_type):
        super().__init__()
        self.target = target
        self.ping_type = ping_type

    def run(self):
        if self.ping_type == "ICMP":
            result = self.icmp_ping()
        elif self.ping_type == "TCP":
            result = self.tcp_ping()
        elif self.ping_type == "HTTP":
            result = self.http_ping()
        elif self.ping_type == "HTTPS":
            result = self.https_ping()
        elif self.ping_type == "JavaMC":
            result = self.java_mc_ping()
        elif self.ping_type == "BedrockMC":
            result = self.bedrock_mc_ping()
        else:
            result = None

        if result is not None:
            self.update_signal.emit(self.target, result)

    def icmp_ping(self):
        try:
            output = subprocess.check_output(["ping", "-n", "4", self.target], universal_newlines=True)
            lines = output.split('\n')
            result = {
                'min': None,
                'max': None,
                'avg': None,
                'loss': None
            }
            for line in lines:
                if "最小 = " in line:
                    parts = line.split(', ')
                    result['min'] = float(parts[0].split('=')[1].strip().replace('ms', ''))
                    result['max'] = float(parts[1].split('=')[1].strip().replace('ms', ''))
                    result['avg'] = float(parts[2].split('=')[1].strip().replace('ms', ''))
                elif "丢失 = " in line:
                    result['loss'] = int(line.split('(')[1].split('%')[0])
            return result
        except subprocess.CalledProcessError:
            return None

    def tcp_ping(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        start_time = time.time()
        try:
            sock.connect((self.target, 80))
            return (time.time() - start_time) * 1000
        except socket.error:
            return None
        finally:
            sock.close()


    def http_ping(self):
        try:
            start_time = time.time()
            requests.get(f"http://{self.target}", timeout=5)
            return (time.time() - start_time) * 1000
        except requests.RequestException:
            return None

    def https_ping(self):
        try:
            start_time = time.time()
            requests.get(f"https://{self.target}", timeout=5, verify=False)
            return (time.time() - start_time) * 1000
        except requests.RequestException:
            return None

    def java_mc_ping(self):
        try:
            server = JavaServer.lookup(self.target)
            status = server.status()
            return {
                '延迟': status.latency,
                '版本': status.version.name,
                '协议': status.version.protocol,
                '在线玩家': status.players.online,
                '最大玩家': status.players.max,
                '描述': status.description
            }
        except Exception as e:
            return f"错误: {str(e)}"

    def bedrock_mc_ping(self):
        try:
            server = BedrockServer.lookup(self.target)
            status = server.status()
            return {
                '延迟': status.latency,
                '版本': status.version.name,
                '协议': status.version.protocol,
                '在线玩家': status.players_online,
                '最大玩家': status.players_max,
                '游戏模式': status.gamemode,
                '地图': status.map
            }
        except Exception as e:
            return f"错误: {str(e)}"


class NodeSelectionDialog(QDialog):
    """节点选择对话框"""

    def __init__(self, nodes, parent=None):
        super().__init__(parent)
        self.nodes = nodes
        self.selected_nodes = []
        self.initUI()

    def initUI(self):
        self.setWindowTitle("选择节点")
        layout = QVBoxLayout()

        splitter = QSplitter(Qt.Orientation.Horizontal)

        self.available_list = QListWidget()
        self.selected_list = QListWidget()

        for node in self.nodes:
            self.available_list.addItem(node['name'])

        splitter.addWidget(self.available_list)
        splitter.addWidget(self.selected_list)

        layout.addWidget(splitter)

        button_layout = QHBoxLayout()
        ok_button = QPushButton("确定")
        ok_button.clicked.connect(self.accept)
        cancel_button = QPushButton("取消")
        cancel_button.clicked.connect(self.reject)
        button_layout.addWidget(ok_button)
        button_layout.addWidget(cancel_button)

        layout.addLayout(button_layout)
        self.setLayout(layout)

        self.available_list.itemClicked.connect(self.add_node)
        self.selected_list.itemDoubleClicked.connect(self.remove_node)

    def add_node(self, item):
        self.available_list.takeItem(self.available_list.row(item))
        self.selected_list.addItem(item.text())

    def remove_node(self, item):
        self.selected_list.takeItem(self.selected_list.row(item))
        self.available_list.addItem(item.text())

    def accept(self):
        self.selected_nodes = [self.selected_list.item(i).text() for i in range(self.selected_list.count())]
        if not self.selected_nodes:
            QMessageBox.warning(self, "警告", "请至少选择一个节点")
        else:
            super().accept()


class WorkerThread(QThread):
    """工作线程"""
    update_signal = pyqtSignal(str)

    def __init__(self, function, *args, **kwargs):
        super().__init__()
        self.function = function
        self.args = args
        self.kwargs = kwargs

    def run(self):
        result = self.function(*self.args, **self.kwargs)
        self.update_signal.emit(str(result))

class BaseCard(QFrame):
    clicked = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.setFrameShape(QFrame.Shape.StyledPanel)
        self.setFrameShadow(QFrame.Shadow.Raised)
        self.setAttribute(Qt.WidgetAttribute.WA_Hover)
        self.setStyleSheet("""
            BaseCard {
                background-color: #f0f0f0;
                border: 1px solid #d0d0d0;
                border-radius: 5px;
            }
            BaseCard:hover {
                background-color: #e0e0e0;
                border: 1px solid #c0c0c0;
            }
        """)

class TunnelCard(QFrame):
    clicked = pyqtSignal(object, bool)
    start_stop_signal = pyqtSignal(object, bool)

    def __init__(self, tunnel_info):
        super().__init__()
        self.tunnel_info = tunnel_info
        self.is_running = False
        self.is_selected = False
        self.initUI()
        self.updateStyle()

    def initUI(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(10, 10, 10, 10)

        name_label = QLabel(f"<b>{self.tunnel_info.get('name', 'Unknown')}</b>")
        name_label.setObjectName("nameLabel")
        type_label = QLabel(f"类型: {self.tunnel_info.get('type', 'Unknown')}")
        local_label = QLabel(
            f"本地: {self.tunnel_info.get('localip', 'Unknown')}:{self.tunnel_info.get('nport', 'Unknown')}")
        remote_label = QLabel(f"远程端口: {self.tunnel_info.get('dorp', 'Unknown')}")
        node_label = QLabel(f"节点: {self.tunnel_info.get('node', 'Unknown')}")

        self.status_label = QLabel("状态: 未启动")
        self.start_stop_button = QPushButton("启动")
        self.start_stop_button.clicked.connect(self.toggle_start_stop)

        layout.addWidget(name_label)
        layout.addWidget(type_label)
        layout.addWidget(local_label)
        layout.addWidget(remote_label)
        layout.addWidget(node_label)
        layout.addWidget(self.status_label)
        layout.addWidget(self.start_stop_button)

        self.setLayout(layout)
        self.setFixedSize(250, 220)

    def toggle_start_stop(self):
        self.is_running = not self.is_running
        self.update_status()
        self.start_stop_signal.emit(self.tunnel_info, self.is_running)

    def update_status(self):
        if self.is_running:
            self.status_label.setText("状态: 运行中")
            self.start_stop_button.setText("停止")
        else:
            self.status_label.setText("状态: 未启动")
            self.start_stop_button.setText("启动")
        self.update()

    def paintEvent(self, event):
        super().paintEvent(event)
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        if self.is_running:
            color = QColor(0, 255, 0)  # 绿色
        else:
            color = QColor(255, 0, 0)  # 红色
        painter.setPen(QPen(color, 2))
        painter.setBrush(color)
        painter.drawEllipse(self.width() - 20, 10, 10, 10)

    def updateStyle(self):
        self.setStyleSheet("""
            TunnelCard {
                border: 1px solid #d0d0d0;
                border-radius: 5px;
                padding: 10px;
                margin: 5px;
            }
            TunnelCard:hover {
                background-color: rgba(240, 240, 240, 50);
            }
            #nameLabel {
                font-size: 16px;
                font-weight: bold;
            }
        """)

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.is_selected = not self.is_selected
            self.setSelected(self.is_selected)
            self.clicked.emit(self.tunnel_info, self.is_selected)
        super().mousePressEvent(event)

    def setSelected(self, selected):
        self.is_selected = selected
        if selected:
            self.setStyleSheet(self.styleSheet() + "TunnelCard { border: 2px solid #0066cc; background-color: rgba(224, 224, 224, 50); }")
        else:
            self.setStyleSheet(self.styleSheet().replace("TunnelCard { border: 2px solid #0066cc; background-color: rgba(224, 224, 224, 50); }", ""))


class BatchEditDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("批量编辑隧道")
        self.layout = QVBoxLayout(self)

        self.node_combo = QComboBox()
        self.node_combo.addItem("不修改")
        self.node_combo.addItems([node['name'] for node in get_nodes()])

        self.type_combo = QComboBox()
        self.type_combo.addItem("不修改")
        self.type_combo.addItems(["tcp", "udp", "http", "https"])

        self.local_ip_input = QLineEdit()
        self.local_ip_input.setPlaceholderText("不修改")

        self.local_port_input = QLineEdit()
        self.local_port_input.setPlaceholderText("不修改")

        self.remote_port_input = QLineEdit()
        self.remote_port_input.setPlaceholderText("不修改")

        form_layout = QFormLayout()
        form_layout.addRow("节点:", self.node_combo)
        form_layout.addRow("类型:", self.type_combo)
        form_layout.addRow("本地IP:", self.local_ip_input)
        form_layout.addRow("本地端口:", self.local_port_input)
        form_layout.addRow("远程端口:", self.remote_port_input)

        self.layout.addLayout(form_layout)

        buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        self.layout.addWidget(buttons)

    def get_changes(self):
        changes = {}
        if self.node_combo.currentIndex() != 0:
            changes['node'] = self.node_combo.currentText()
        if self.type_combo.currentIndex() != 0:
            changes['type'] = self.type_combo.currentText()
        if self.local_ip_input.text():
            changes['localip'] = self.local_ip_input.text()
        if self.local_port_input.text():
            changes['nport'] = int(self.local_port_input.text())
        if self.remote_port_input.text():
            changes['dorp'] = int(self.remote_port_input.text())
        return changes




class DomainCard(QFrame):
    clicked = pyqtSignal(object)

    def __init__(self, domain_info):
        super().__init__()
        self.domain_info = domain_info
        self.initUI()
        self.updateStyle()

    def initUI(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(10, 10, 10, 10)

        domain_label = QLabel(f"<b>{self.domain_info['record']}.{self.domain_info['domain']}</b>")
        domain_label.setObjectName("nameLabel")
        type_label = QLabel(f"类型: {self.domain_info['type']}")
        target_label = QLabel(f"目标: {self.domain_info['target']}")
        ttl_label = QLabel(f"TTL: {self.domain_info['ttl']}")
        remarks_label = QLabel(f"备注: {self.domain_info.get('remarks', '无')}")

        layout.addWidget(domain_label)
        layout.addWidget(type_label)
        layout.addWidget(target_label)
        layout.addWidget(ttl_label)
        layout.addWidget(remarks_label)

        self.setLayout(layout)
        self.setFixedSize(250, 180)

    def updateStyle(self):
        self.setStyleSheet("""
            DomainCard {
                border: 1px solid #d0d0d0;
                border-radius: 5px;
                padding: 10px;
                margin: 5px;
            }
            DomainCard:hover {
                background-color: rgba(240, 240, 240, 50);
            }
            #nameLabel {
                font-size: 16px;
                font-weight: bold;
            }
        """)

    def setSelected(self, selected):
        if selected:
            self.setStyleSheet(self.styleSheet() + "DomainCard { border: 2px solid #0066cc; background-color: rgba(224, 224, 224, 50); }")
        else:
            self.setStyleSheet(self.styleSheet().replace("DomainCard { border: 2px solid #0066cc; background-color: rgba(224, 224, 224, 50); }", ""))

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.clicked.emit(self.domain_info)
        super().mousePressEvent(event)




class ConfigEditorDialog(QDialog):
    def __init__(self, config_file_path, dark_theme, parent=None):
        super().__init__(parent)
        self.config_file_path = config_file_path
        self.dark_theme = dark_theme
        self.initUI()
        self.apply_theme()

    def initUI(self):
        self.setWindowTitle("编辑配置文件")
        self.setGeometry(100, 100, 600, 400)

        layout = QVBoxLayout(self)

        # 创建选项卡小部件
        self.tab_widget = QTabWidget()
        layout.addWidget(self.tab_widget)

        # 文本编辑选项卡
        self.text_edit = QTextEdit()
        self.tab_widget.addTab(self.text_edit, "文本编辑")

        # 可视化编辑选项卡
        visual_edit_widget = QWidget()
        visual_edit_layout = QFormLayout(visual_edit_widget)
        self.tab_widget.addTab(visual_edit_widget, "可视化编辑")

        # 添加可视化编辑的输入字段
        self.token_input = QLineEdit()
        self.nodes_input = QLineEdit()
        self.tunnel_name_input = QLineEdit()
        self.domain_input = QLineEdit()
        self.subdomain_input = QLineEdit()
        self.record_type_combo = QComboBox()
        self.record_type_combo.addItems(["A", "SRV"])

        visual_edit_layout.addRow("Token:", self.token_input)
        visual_edit_layout.addRow("节点 (逗号分隔):", self.nodes_input)
        visual_edit_layout.addRow("隧道名称:", self.tunnel_name_input)
        visual_edit_layout.addRow("域名:", self.domain_input)
        visual_edit_layout.addRow("子域名:", self.subdomain_input)
        visual_edit_layout.addRow("记录类型:", self.record_type_combo)

        # SRV 特定输入
        self.srv_widget = QWidget()
        srv_layout = QFormLayout(self.srv_widget)
        self.priority_input = QLineEdit("10")
        self.weight_input = QLineEdit("10")
        self.port_input = QLineEdit()
        srv_layout.addRow("优先级:", self.priority_input)
        srv_layout.addRow("权重:", self.weight_input)
        srv_layout.addRow("端口:", self.port_input)
        visual_edit_layout.addRow(self.srv_widget)

        self.record_type_combo.currentTextChanged.connect(self.on_record_type_changed)

        # 添加保存和取消按钮
        button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)

        self.load_config()

    def apply_theme(self):
        if self.dark_theme:
            self.setStyleSheet("""
                QDialog, QTabWidget, QTextEdit, QLineEdit, QComboBox {
                    background-color: #2D2D2D;
                    color: #FFFFFF;
                }
                QTabWidget::pane {
                    border: 1px solid #555555;
                }
                QTabBar::tab {
                    background-color: #3D3D3D;
                    color: #FFFFFF;
                    padding: 5px;
                }
                QTabBar::tab:selected {
                    background-color: #4D4D4D;
                }
                QPushButton {
                    background-color: #0D47A1;
                    color: white;
                    border: none;
                    padding: 5px 10px;
                    text-align: center;
                    text-decoration: none;
                    font-size: 14px;
                    margin: 4px 2px;
                    border-radius: 4px;
                }
                QPushButton:hover {
                    background-color: #1565C0;
                }
                QLabel {
                    color: #FFFFFF;
                }
            """)
        else:
            self.setStyleSheet("")  # 使用默认浅色主题

    def on_record_type_changed(self):
        self.srv_widget.setVisible(self.record_type_combo.currentText() == "SRV")

    def load_config(self):
        with open(self.config_file_path, 'r') as f:
            config_content = f.read()
        self.text_edit.setPlainText(config_content)

        try:
            config = ast.literal_eval(config_content)
            if isinstance(config, list) and len(config) >= 6:
                self.token_input.setText(config[0])
                self.nodes_input.setText(", ".join(config[2:2+int(config[1])]))
                self.tunnel_name_input.setText(config[2+int(config[1])])
                self.domain_input.setText(config[3+int(config[1])])
                self.subdomain_input.setText(config[4+int(config[1])])
                self.record_type_combo.setCurrentText(config[5+int(config[1])])
                if len(config) > 6+int(config[1]) and config[5+int(config[1])] == "SRV":
                    self.priority_input.setText(config[6+int(config[1])])
                    self.weight_input.setText(config[7+int(config[1])])
                    self.port_input.setText(config[8+int(config[1])])
        except:
            pass

    def get_config(self):
        if self.tab_widget.currentIndex() == 0:
            # 文本编辑模式
            return self.text_edit.toPlainText()
        else:
            # 可视化编辑模式
            nodes = [node.strip() for node in self.nodes_input.text().split(',')]
            config = [
                self.token_input.text(),
                len(nodes),
                *nodes,
                self.tunnel_name_input.text(),
                self.domain_input.text(),
                self.subdomain_input.text(),
                self.record_type_combo.currentText()
            ]
            if self.record_type_combo.currentText() == "SRV":
                config.extend([
                    self.priority_input.text(),
                    self.weight_input.text(),
                    self.port_input.text()
                ])
            return str(config)


class ConfigListItem(QListWidgetItem):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.update_display()

    def update_display(self):
        if isinstance(self.config, dict):
            display_text = (f"隧道: {self.config.get('tunnel_name', 'N/A')}\n"
                            f"域名: {self.config.get('subdomain', 'N/A')}.{self.config.get('domain', 'N/A')}")
        else:
            display_text = str(self.config)
        self.setText(display_text)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值