文章详情

返回首页

CF搭建节点管理v2.0

分享文章 作者: Ws01 创建时间: 2025-11-27 更新时间: 2025-12-28 📝 字数: 94,276 字 👁️ 阅读: 64 次
CF搭建节点管理v2.0
// 部署完成后在网址后面加上这个,获取自建节点和机场聚合节点,/?token=xxoo&tag=9527abc-jichang
// 新增:仅获取机场聚合节点,/?token=xxoo&tag=9527abc-jichang-only
// 默认节点信息,聚合订阅地址:https://域名/?token=5758bf7a-87ad-4b69-a48c-c9c0bd4cfc1f&tag=9527abc-jichang
// 部署完成后在网址后面加上这个,只获取自建节点,/?token=xxoo
// 登录管理页面:https://域名/9527kkk/login
// CF的kv数据库邦定名称NODES_KV

const mytoken = '5758bf7a-87ad-4b69-a48c-c9c0bd4cfc1f'; //可以随便取,或者uuid生成,https://1024tools.com/uuid
const SUBSCRIPTION_TAGS = {
  COMBINED: '9527abc-jichang',      // 自建 + 机场
  AIRPORT_ONLY: '9527abc-jichang-only' // 仅机场
};
const tgbottoken =''; //可以为空,或者@BotFather中输入/start,/newbot,并关注机器人
const tgchatid =''; //可以为空,或者@userinfobot中获取,/start

// 登录认证配置
const LOGIN_USERNAME = 'admin'; // 默认用户名,设置中修改,添加变量名USERNAME
const LOGIN_PASSWORD = 'admin888'; // 默认密码,设置中修改,添加变量名PASSWORD
const LOGIN_PATH = '/9527kkk/login'; // 登录页面路径
const ADMIN_PATH = '/9527kkk/'; // 管理页面路径

// 从环境变量获取登录凭据(如果设置了的话)
const ENV_USERNAME = typeof USERNAME !== 'undefined' ? USERNAME : LOGIN_USERNAME;
const ENV_PASSWORD = typeof PASSWORD !== 'undefined' ? PASSWORD : LOGIN_PASSWORD;

// KV存储键名
const KV_KEYS = {
  CUSTOM_NODES: 'custom_nodes',
  SUBSCRIPTION_URLS: 'subscription_urls',
  AUTH_SESSIONS: 'auth_sessions'
};

// 内存存储作为fallback
let memoryStorage = {
  custom_nodes: [],
  subscription_urls: [],
  auth_sessions: {}
};

// 创建fallback存储对象
let fallbackStorage = {
  async get(key) {
    console.log(`KV Get (fallback): ${key}`);
    return memoryStorage[key] ? JSON.stringify(memoryStorage[key]) : null;
  },
  async put(key, value) {
    console.log(`KV Put (fallback): ${key} = ${value}`);
    try {
      memoryStorage[key] = JSON.parse(value);
      return true;
    } catch (error) {
      console.error('Memory storage error:', error);
      return false;
    }
  },
  async delete(key) {
    console.log(`KV Delete (fallback): ${key}`);
    delete memoryStorage[key];
    return true;
  }
};

// 检查KV绑定状态
console.log('检查KV绑定状态...');

// 检查是否已经有KV绑定(Cloudflare会自动注入绑定的变量)
let usingRealKV = false;

// 方法1: 检查全局变量NODES_KV是否被Cloudflare注入
if (typeof NODES_KV !== 'undefined' && NODES_KV !== fallbackStorage) {
  usingRealKV = true;
  console.log('✅ 检测到KV绑定 (方法1) - 数据将持久保存');
}

// 方法2: 检查是否有KV绑定对象
if (!usingRealKV && typeof NODES_KV_BINDING !== 'undefined') {
  NODES_KV = NODES_KV_BINDING;
  usingRealKV = true;
  console.log('✅ 检测到KV绑定 (方法2) - 数据将持久保存');
}

// 方法3: 尝试直接访问绑定的变量
if (!usingRealKV) {
  try {
    // 在Cloudflare Workers中,绑定的变量会直接可用
    if (typeof NODES_KV !== 'undefined' && NODES_KV && typeof NODES_KV.get === 'function') {
      usingRealKV = true;
      console.log('✅ 检测到KV绑定 (方法3) - 数据将持久保存');
    }
  } catch (error) {
    console.log('KV检测方法3失败:', error);
  }
}

if (!usingRealKV) {
  NODES_KV = fallbackStorage;
  console.log('⚠️ 使用内存存储fallback - 数据在Worker重启后会丢失');
  console.log('请确保在Worker设置中正确绑定了KV存储,变量名为: NODES_KV');
  console.log('当前NODES_KV类型:', typeof NODES_KV);
  console.log('当前NODES_KV值:', NODES_KV);
}

addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) })


async function handleRequest(request) {
    const url = new URL(request.url);
    const pathname = url.pathname;
    const token = url.searchParams.get('token');
    const tag = url.searchParams.get('tag');

    // 处理静态资源请求(不需要token验证)
    if (pathname === '/favicon.ico' || pathname.startsWith('/static/') || pathname.endsWith('.css') || pathname.endsWith('.js')) {
      return new Response('', { status: 404 });
    }

    // 登录页面路由
    if (pathname === LOGIN_PATH) {
      return handleLoginPage(request);
    }

    // 登录API路由
    if (pathname === LOGIN_PATH + '/auth') {
      return handleLoginAuth(request);
    }

    // 登出API路由
    if (pathname === LOGIN_PATH + '/logout') {
      return handleLogout(request);
    }

    // 管理页面路由(需要登录验证)
    if (pathname === ADMIN_PATH) {
      return handleAdminPageWithAuth(request);
    }

    // 旧的管理页面路由(保持兼容性,但需要token验证)
    if (pathname === '/admin') {
      if (token !== mytoken) {
        return new Response('Invalid token???', { status: 403 });
      }
      return handleAdminPage(request);
    }

    // API路由(需要登录验证)
    if (pathname.startsWith('/api/')) {
      return handleAPIWithAuth(request);
    }

    // 原有的节点订阅逻辑(保持原有token验证)
    if (token !== mytoken) {
      return new Response('Invalid token???', { status: 403 });
    }

    return handleSubscription(request, tag);
}

async function handleAdminPage(request) {
  const html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节点管理后台</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚙️</text></svg>">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; }
        .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
        .header { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        .card { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        .form-group { margin-bottom: 15px; }
        .form-group label { display: block; margin-bottom: 5px; font-weight: 500; color: #333; }
        .form-group input, .form-group textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
        .form-group textarea { height: 100px; resize: vertical; }
        .btn { background: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; margin-right: 10px; }
        .btn:hover { background: #0056b3; }
        .btn-danger { background: #dc3545; }
        .btn-danger:hover { background: #c82333; }
        .btn-success { background: #28a745; }
        .btn-success:hover { background: #218838; }
        .list-item { background: #f8f9fa; padding: 15px; margin-bottom: 10px; border-radius: 4px; border-left: 4px solid #007bff; }
        .list-item h4 { margin-bottom: 5px; color: #333; }
        .list-item p { color: #666; font-size: 14px; margin-bottom: 10px; }
        .actions { display: flex; gap: 10px; }
        
        /* 表格样式 */
        .table-container { overflow-x: auto; margin-top: 20px; }
        .nodes-table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        .nodes-table th { background: #f8f9fa; color: #333; font-weight: 600; padding: 12px 15px; text-align: left; border-bottom: 2px solid #dee2e6; }
        .nodes-table td { padding: 12px 15px; border-bottom: 1px solid #dee2e6; vertical-align: top; }
        .nodes-table tr:hover { background: #f8f9fa; }
        .nodes-table tr:last-child td { border-bottom: none; }
        .node-index { width: 100px; text-align: center; font-weight: 600; color: #007bff; }
        .node-name { width: 200px; font-weight: 500; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .node-config { font-family: monospace; font-size: 12px; color: #666; word-break: break-all; max-width: 400px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .node-actions { width: 220px; text-align: center; }
        .btn-sm { padding: 6px 12px; font-size: 12px; }
        
        /* 机场订阅表格样式 */
        .subscription-table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        .subscription-table th { background: #f8f9fa; color: #333; font-weight: 600; padding: 12px 15px; text-align: left; border-bottom: 2px solid #dee2e6; }
        .subscription-table td { padding: 12px 15px; border-bottom: 1px solid #dee2e6; vertical-align: top; }
        .subscription-table tr:hover { background: #f8f9fa; }
        .subscription-table tr:last-child td { border-bottom: none; }
        .sub-index { width: 100px; text-align: center; font-weight: 600; color: #007bff; }
        .sub-name { width: 200px; font-weight: 500; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .sub-url { font-family: monospace; font-size: 12px; color: #666; word-break: break-all; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .sub-actions { width: 220px; text-align: center; }
        .status { padding: 5px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; }
        .status.success { background: #d4edda; color: #155724; }
        .status.error { background: #f8d7da; color: #721c24; }
        .status.warning { background: #fff3cd; color: #856404; }
        .tabs { display: flex; margin-bottom: 20px; }
        .tab { padding: 10px 20px; background: #e9ecef; border: none; cursor: pointer; border-radius: 4px 4px 0 0; margin-right: 5px; }
        .tab.active { background: #007bff; color: white; }
        .tab-content { display: none; }
        .tab-content.active { display: block; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
                <div>
                    <h1>节点管理后台 v2.0</h1>
                    <p>管理自建节点和机场订阅链接</p>
                </div>
                <div style="text-align: right;">
                    <span id="user-info" style="color: #666; font-size: 14px;">欢迎,管理员</span>
                    <br>
                    <button onclick="logout()" class="btn btn-danger btn-sm" style="margin-top: 5px;">登出</button>
                </div>
            </div>
            <div id="storage-status" style="margin-top: 10px; padding: 8px; border-radius: 4px; font-size: 14px;">
                <span id="status-text">检查存储状态中...</span>
            </div>
        </div>

        <div class="tabs">
            <button class="tab active" onclick="switchTab('custom')">自建节点</button>
            <button class="tab" onclick="switchTab('subscription')">机场订阅</button>
        </div>

        <!-- 自建节点管理 -->
        <div id="custom-tab" class="tab-content active">
            <div class="card">
                <h3>添加自建节点</h3>
                <form id="custom-form">
                    <div class="form-group">
                        <label>节点配置 (支持多个节点,每行一个)</label>
                        <textarea id="custom-config" placeholder="粘贴节点配置内容,支持多个节点,每行一个...&#10;支持:VLESS、VMess、Shadowsocks、Trojan等&#10;支持Base64编码的节点配置" required></textarea>
                    </div>
                    <button type="submit" class="btn btn-success">添加节点</button>
                </form>
            </div>

            <div class="card">
                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
                    <h3>自建节点列表</h3>
                    <div>
                        <button id="select-all-btn" class="btn btn-sm" onclick="toggleSelectAll()" style="margin-right: 10px;">全选</button>
                        <button id="batch-pause-btn" class="btn btn-sm" onclick="batchPauseNodes()" style="background: #ffc107; color: white; margin-right: 10px;" disabled>暂停选中</button>
                        <button id="batch-enable-btn" class="btn btn-sm" onclick="batchEnableNodes()" style="background: #28a745; color: white; margin-right: 10px;" disabled>启用选中</button>
                        <button id="batch-delete-btn" class="btn btn-danger btn-sm" onclick="batchDeleteNodes()" disabled>批量删除</button>
                    </div>
                </div>
                <div class="table-container">
                    <table class="nodes-table">
                        <thead>
                            <tr>
                                <th class="node-checkbox" style="width: 50px;">
                                    <input type="checkbox" id="select-all-checkbox" onchange="handleSelectAllChange()">
                                </th>
                                <th class="node-index">序号</th>
                                <th class="node-name">节点名称</th>
                                <th class="node-status" style="width: 100px; text-align: center;">状态</th>
                                <th class="node-config">节点配置</th>
                                <th class="node-actions">操作</th>
                            </tr>
                        </thead>
                        <tbody id="custom-list">
                            <tr>
                                <td colspan="6" style="text-align: center; padding: 20px; color: #666;">加载中...</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>

        <!-- 机场订阅管理 -->
        <div id="subscription-tab" class="tab-content">
            <div class="card">
                <h3>添加机场订阅</h3>
                <form id="subscription-form">
                    <div class="form-group">
                        <label>订阅名称</label>
                        <input type="text" id="subscription-name" placeholder="例如:机场A" required>
                    </div>
                    <div class="form-group">
                        <label>订阅链接</label>
                        <input type="url" id="subscription-url" placeholder="https://example.com/subscription" required>
                    </div>
                    <button type="submit" class="btn btn-success">添加订阅</button>
                </form>
            </div>

            <div class="card">
                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
                    <h3>机场订阅列表</h3>
                    <div>
                        <button id="select-all-sub-btn" class="btn btn-sm" onclick="toggleSelectAllSub()" style="margin-right: 10px;">全选</button>
                        <button id="batch-pause-sub-btn" class="btn btn-sm" onclick="batchPauseSubscriptions()" style="background: #ffc107; color: white; margin-right: 10px;" disabled>暂停选中</button>
                        <button id="batch-enable-sub-btn" class="btn btn-sm" onclick="batchEnableSubscriptions()" style="background: #28a745; color: white; margin-right: 10px;" disabled>启用选中</button>
                        <button id="batch-delete-sub-btn" class="btn btn-danger btn-sm" onclick="batchDeleteSubscriptions()" disabled>批量删除</button>
                    </div>
                </div>
                <div class="table-container">
                    <table class="subscription-table">
                        <thead>
                            <tr>
                                <th class="sub-checkbox" style="width: 50px;">
                                    <input type="checkbox" id="select-all-sub-checkbox" onchange="handleSelectAllSubChange()">
                                </th>
                                <th class="sub-index">序号</th>
                                <th class="sub-name">订阅名称</th>
                                <th class="sub-status" style="width: 100px; text-align: center;">状态</th>
                                <th class="sub-url">订阅链接</th>
                                <th class="sub-actions">操作</th>
                            </tr>
                        </thead>
                        <tbody id="subscription-list">
                            <tr>
                                <td colspan="6" style="text-align: center; padding: 20px; color: #666;">加载中...</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>

    <script>
        let currentTab = 'custom';
        
        function switchTab(tab) {
            // 隐藏所有标签页
            document.querySelectorAll('.tab-content').forEach(content => {
                content.classList.remove('active');
            });
            document.querySelectorAll('.tab').forEach(tab => {
                tab.classList.remove('active');
            });
            
            // 显示选中的标签页
            document.getElementById(tab + '-tab').classList.add('active');
            event.target.classList.add('active');
            currentTab = tab;
        }

        // 加载数据
        async function loadData() {
            await loadCustomNodes();
            await loadSubscriptions();
        }

        // 加载自建节点
        async function loadCustomNodes() {
            try {
                const response = await fetch('/api/custom-nodes');
                const data = await response.json();
                const tbody = document.getElementById('custom-list');
                
                if (data.length === 0) {
                    tbody.innerHTML = '<tr><td colspan="6" style="text-align: center; padding: 20px; color: #666;">暂无自建节点</td></tr>';
                    return;
                }

                tbody.innerHTML = data.map((node, index) => {
                    const isEnabled = node.enabled !== false; // 默认为启用(向后兼容)
                    const statusClass = isEnabled ? 'success' : 'warning';
                    const statusText = isEnabled ? '启用' : '暂停';
                    return \`
                    <tr style="\${!isEnabled ? 'opacity: 0.6;' : ''}">
                        <td class="node-checkbox" style="text-align: center;">
                            <input type="checkbox" class="node-checkbox-input" value="\${node.id}" onchange="handleNodeCheckboxChange()">
                        </td>
                        <td class="node-index">\${index + 1}</td>
                        <td class="node-name" title="\${node.name}">\${truncateText(node.name, 20)}</td>
                        <td class="node-status" style="text-align: center;">
                            <span class="status \${statusClass}">\${statusText}</span>
                        </td>
                        <td class="node-config" title="\${node.config}">\${truncateText(node.config, 50)}</td>
                        <td class="node-actions">
                            <button class="btn btn-sm" onclick="editCustomNode('\${node.id}')" style="background: #28a745; color: white; margin-right: 5px;">编辑</button>
                            <button class="btn btn-sm" onclick="copyCustomNode('\${node.id}')" style="background: #17a2b8; color: white; margin-right: 5px;">复制</button>
                            <button class="btn btn-danger btn-sm" onclick="deleteCustomNode('\${node.id}')">删除</button>
                        </td>
                    </tr>
                \`;
                }).join('');
            } catch (error) {
                console.error('Load custom nodes error:', error);
                document.getElementById('custom-list').innerHTML = '<tr><td colspan="6" style="text-align: center; padding: 20px; color: #dc3545;">加载失败: ' + error.message + '</td></tr>';
            }
        }

        // 加载机场订阅
        async function loadSubscriptions() {
            try {
                const response = await fetch('/api/subscriptions');
                const data = await response.json();
                const tbody = document.getElementById('subscription-list');
                
                if (data.length === 0) {
                    tbody.innerHTML = '<tr><td colspan="6" style="text-align: center; padding: 20px; color: #666;">暂无机场订阅</td></tr>';
                    return;
                }

                tbody.innerHTML = data.map((sub, index) => {
                    const isEnabled = sub.enabled !== false; // 默认为启用(向后兼容)
                    const statusClass = isEnabled ? 'success' : 'warning';
                    const statusText = isEnabled ? '启用' : '暂停';
                    return \`
                    <tr style="\${!isEnabled ? 'opacity: 0.6;' : ''}">
                        <td class="sub-checkbox" style="text-align: center;">
                            <input type="checkbox" class="sub-checkbox-input" value="\${sub.id}" onchange="handleSubCheckboxChange()">
                        </td>
                        <td class="sub-index">\${index + 1}</td>
                        <td class="sub-name" title="\${sub.name}">\${truncateText(sub.name, 20)}</td>
                        <td class="sub-status" style="text-align: center;">
                            <span class="status \${statusClass}">\${statusText}</span>
                        </td>
                        <td class="sub-url" title="\${sub.url}">\${truncateText(sub.url, 50)}</td>
                        <td class="sub-actions">
                            <button class="btn btn-sm" onclick="editSubscription('\${sub.id}')" style="background: #28a745; color: white; margin-right: 5px;">编辑</button>
                            <button class="btn btn-sm" onclick="copySubscription('\${sub.id}')" style="background: #17a2b8; color: white; margin-right: 5px;">复制</button>
                            <button class="btn btn-danger btn-sm" onclick="deleteSubscription('\${sub.id}')">删除</button>
                        </td>
                    </tr>
                \`;
                }).join('');
                
                // 更新批量操作按钮状态
                updateBatchSubButton();
            } catch (error) {
                console.error('Load subscriptions error:', error);
                document.getElementById('subscription-list').innerHTML = '<tr><td colspan="6" style="text-align: center; padding: 20px; color: #dc3545;">加载失败: ' + error.message + '</td></tr>';
            }
        }

        // 添加自建节点
        document.getElementById('custom-form').addEventListener('submit', async (e) => {
            e.preventDefault();
            const config = document.getElementById('custom-config').value.trim();
            
            if (!config) {
                showStatus('请输入节点配置', 'error');
                return;
            }
            
            try {
                const response = await fetch('/api/custom-nodes', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ config })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    // 显示详细的添加结果
                    if (result.duplicateCount > 0) {
                        showStatus(result.message, 'warning');
                        console.log('重复的节点:', result.duplicates);
                    } else {
                        showStatus(result.message, 'success');
                    }
                    
                    document.getElementById('custom-form').reset();
                    loadCustomNodes();
                } else {
                    showStatus(result.error || '添加失败', 'error');
                    console.error('Add custom node error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        });

        // 添加机场订阅
        document.getElementById('subscription-form').addEventListener('submit', async (e) => {
            e.preventDefault();
            const name = document.getElementById('subscription-name').value;
            const url = document.getElementById('subscription-url').value;
            
            try {
                const response = await fetch('/api/subscriptions', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ name, url })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus(result.message || '添加成功', 'success');
                    document.getElementById('subscription-form').reset();
                    loadSubscriptions();
                } else {
                    showStatus(result.error || '添加失败', 'error');
                    console.error('Add subscription error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        });

        // 删除自建节点
        async function deleteCustomNode(id) {
            if (!confirm('确定要删除这个节点吗?')) return;
            
            try {
                const response = await fetch(\`/api/custom-nodes/\${id}\`, { method: 'DELETE' });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('删除成功', 'success');
                    loadCustomNodes();
                } else {
                    showStatus(result.error || '删除失败', 'error');
                    console.error('Delete custom node error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 删除机场订阅
        async function deleteSubscription(id) {
            if (!confirm('确定要删除这个订阅吗?')) return;
            
            try {
                const response = await fetch(\`/api/subscriptions/\${id}\`, { method: 'DELETE' });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('删除成功', 'success');
                    loadSubscriptions();
                } else {
                    showStatus(result.error || '删除失败', 'error');
                    console.error('Delete subscription error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 截断文本显示
        function truncateText(text, maxLength) {
            if (text.length <= maxLength) {
                return text;
            }
            return text.substring(0, maxLength) + '...';
        }

        // 显示状态消息
        function showStatus(message, type) {
            const status = document.createElement('div');
            status.className = \`status \${type}\`;
            status.textContent = message;
            status.style.position = 'fixed';
            status.style.top = '20px';
            status.style.right = '20px';
            status.style.zIndex = '1000';
            document.body.appendChild(status);
            
            setTimeout(() => {
                status.remove();
            }, 3000);
        }

        // 检查存储状态
        async function checkStorageStatus() {
            try {
                const response = await fetch('/api/storage-status');
                const result = await response.json();
                
                const statusDiv = document.getElementById('storage-status');
                const statusText = document.getElementById('status-text');
                
                if (result.usingKV) {
                    statusDiv.style.background = '#d4edda';
                    statusDiv.style.color = '#155724';
                    statusDiv.style.border = '1px solid #c3e6cb';
                    statusText.textContent = '✅ 使用KV存储 - 数据将持久保存';
                } else {
                    statusDiv.style.background = '#fff3cd';
                    statusDiv.style.color = '#856404';
                    statusDiv.style.border = '1px solid #ffeaa7';
                    statusText.innerHTML = '⚠️ 使用内存存储 - 数据在Worker重启后会丢失<br><small>请按照KV配置指南正确绑定KV存储</small>';
                }
            } catch (error) {
                const statusDiv = document.getElementById('storage-status');
                const statusText = document.getElementById('status-text');
                statusDiv.style.background = '#f8d7da';
                statusDiv.style.color = '#721c24';
                statusDiv.style.border = '1px solid #f5c6cb';
                statusText.textContent = '❌ 无法检查存储状态';
            }
        }

        // 全选/取消全选功能
        function toggleSelectAll() {
            const selectAllCheckbox = document.getElementById('select-all-checkbox');
            const nodeCheckboxes = document.querySelectorAll('.node-checkbox-input');
            
            // 检查是否所有节点都被选中
            const allChecked = Array.from(nodeCheckboxes).every(checkbox => checkbox.checked);
            
            // 如果全部选中,则取消全选;否则全选
            const shouldCheck = !allChecked;
            
            nodeCheckboxes.forEach(checkbox => {
                checkbox.checked = shouldCheck;
            });
            
            // 更新全选复选框状态
            selectAllCheckbox.checked = shouldCheck;
            selectAllCheckbox.indeterminate = false;
            
            updateBatchDeleteButton();
        }

        // 处理全选复选框变化
        function handleSelectAllChange() {
            const selectAllCheckbox = document.getElementById('select-all-checkbox');
            const nodeCheckboxes = document.querySelectorAll('.node-checkbox-input');
            
            // 根据全选复选框的状态来设置所有节点复选框
            const isChecked = selectAllCheckbox.checked;
            nodeCheckboxes.forEach(checkbox => {
                checkbox.checked = isChecked;
            });
            
            // 清除indeterminate状态
            selectAllCheckbox.indeterminate = false;
            
            updateBatchDeleteButton();
        }

        // 处理单个节点复选框变化
        function handleNodeCheckboxChange() {
            const selectAllCheckbox = document.getElementById('select-all-checkbox');
            const nodeCheckboxes = document.querySelectorAll('.node-checkbox-input');
            
            // 检查是否所有节点都被选中
            const allChecked = Array.from(nodeCheckboxes).every(checkbox => checkbox.checked);
            const someChecked = Array.from(nodeCheckboxes).some(checkbox => checkbox.checked);
            
            selectAllCheckbox.checked = allChecked;
            selectAllCheckbox.indeterminate = someChecked && !allChecked;
            
            updateBatchDeleteButton();
        }

        // 更新批量操作按钮状态
        function updateBatchDeleteButton() {
            const selectedCheckboxes = document.querySelectorAll('.node-checkbox-input:checked');
            const batchDeleteBtn = document.getElementById('batch-delete-btn');
            const batchPauseBtn = document.getElementById('batch-pause-btn');
            const batchEnableBtn = document.getElementById('batch-enable-btn');
            
            if (selectedCheckboxes.length > 0) {
                batchDeleteBtn.disabled = false;
                batchDeleteBtn.textContent = '批量删除 (' + selectedCheckboxes.length + ')';
                batchPauseBtn.disabled = false;
                batchPauseBtn.textContent = '暂停选中 (' + selectedCheckboxes.length + ')';
                batchEnableBtn.disabled = false;
                batchEnableBtn.textContent = '启用选中 (' + selectedCheckboxes.length + ')';
            } else {
                batchDeleteBtn.disabled = true;
                batchDeleteBtn.textContent = '批量删除';
                batchPauseBtn.disabled = true;
                batchPauseBtn.textContent = '暂停选中';
                batchEnableBtn.disabled = true;
                batchEnableBtn.textContent = '启用选中';
            }
        }

        // 批量删除节点
        async function batchDeleteNodes() {
            const selectedCheckboxes = document.querySelectorAll('.node-checkbox-input:checked');
            const selectedIds = Array.from(selectedCheckboxes).map(checkbox => checkbox.value);
            
            if (selectedIds.length === 0) {
                showStatus('请先选择要删除的节点', 'error');
                return;
            }
            
            if (!confirm('确定要删除选中的 ' + selectedIds.length + ' 个节点吗?')) {
                return;
            }
            
            try {
                // 批量删除请求
                const response = await fetch('/api/custom-nodes/batch-delete', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ ids: selectedIds })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('成功删除 ' + result.deletedCount + ' 个节点', 'success');
                    loadCustomNodes(); // 重新加载节点列表
                } else {
                    showStatus(result.error || '批量删除失败', 'error');
                    console.error('Batch delete error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 批量暂停节点
        async function batchPauseNodes() {
            const selectedCheckboxes = document.querySelectorAll('.node-checkbox-input:checked');
            const selectedIds = Array.from(selectedCheckboxes).map(checkbox => checkbox.value);
            
            if (selectedIds.length === 0) {
                showStatus('请先选择要暂停的节点', 'error');
                return;
            }
            
            if (!confirm('确定要暂停选中的 ' + selectedIds.length + ' 个节点吗?暂停的节点将不会出现在订阅中。')) {
                return;
            }
            
            try {
                const response = await fetch('/api/custom-nodes/batch-toggle', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ ids: selectedIds, enabled: false })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('成功暂停 ' + result.updatedCount + ' 个节点', 'success');
                    loadCustomNodes(); // 重新加载节点列表
                } else {
                    showStatus(result.error || '批量暂停失败', 'error');
                    console.error('Batch pause error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 批量启用节点
        async function batchEnableNodes() {
            const selectedCheckboxes = document.querySelectorAll('.node-checkbox-input:checked');
            const selectedIds = Array.from(selectedCheckboxes).map(checkbox => checkbox.value);
            
            if (selectedIds.length === 0) {
                showStatus('请先选择要启用的节点', 'error');
                return;
            }
            
            if (!confirm('确定要启用选中的 ' + selectedIds.length + ' 个节点吗?')) {
                return;
            }
            
            try {
                const response = await fetch('/api/custom-nodes/batch-toggle', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ ids: selectedIds, enabled: true })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('成功启用 ' + result.updatedCount + ' 个节点', 'success');
                    loadCustomNodes(); // 重新加载节点列表
                } else {
                    showStatus(result.error || '批量启用失败', 'error');
                    console.error('Batch enable error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 复制节点配置
        async function copyCustomNode(nodeId) {
            try {
                // 获取节点数据
                const response = await fetch('/api/custom-nodes');
                const nodes = await response.json();
                const node = nodes.find(n => n.id === nodeId);
                
                if (!node) {
                    showStatus('未找到要复制的节点', 'error');
                    return;
                }
                
                // 复制到剪贴板
                await navigator.clipboard.writeText(node.config);
                showStatus('节点配置已复制到剪贴板', 'success');
            } catch (error) {
                // 如果剪贴板API不可用,使用传统方法
                try {
                    const response = await fetch('/api/custom-nodes');
                    const nodes = await response.json();
                    const node = nodes.find(n => n.id === nodeId);
                    
                    if (node) {
                        // 创建临时文本区域
                        const textArea = document.createElement('textarea');
                        textArea.value = node.config;
                        document.body.appendChild(textArea);
                        textArea.select();
                        document.execCommand('copy');
                        document.body.removeChild(textArea);
                        showStatus('节点配置已复制到剪贴板', 'success');
                    } else {
                        showStatus('未找到要复制的节点', 'error');
                    }
                } catch (fallbackError) {
                    showStatus('复制失败: ' + error.message, 'error');
                    console.error('Copy error:', error);
                }
            }
        }

        // 编辑节点配置
        async function editCustomNode(nodeId) {
            try {
                // 获取节点数据
                const response = await fetch('/api/custom-nodes');
                const nodes = await response.json();
                const node = nodes.find(n => n.id === nodeId);
                
                if (!node) {
                    showStatus('未找到要编辑的节点', 'error');
                    return;
                }
                
                // 显示编辑对话框
                showEditModal(node);
            } catch (error) {
                showStatus('获取节点信息失败: ' + error.message, 'error');
                console.error('Get node error:', error);
            }
        }

        // 显示编辑模态框
        function showEditModal(node) {
            // 创建模态框HTML
            const modalHtml = \`
                <div id="editModal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; display: flex; align-items: center; justify-content: center;">
                    <div style="background: white; padding: 30px; border-radius: 8px; width: 90%; max-width: 600px; max-height: 80vh; overflow-y: auto;">
                        <h3 style="margin-bottom: 20px; color: #333;">编辑节点配置</h3>
                        
                        <div class="form-group">
                            <label>节点名称</label>
                            <input type="text" id="edit-node-name" value="\${node.name}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 15px;">
                        </div>
                        
                        <div class="form-group">
                            <label>节点配置</label>
                            <textarea id="edit-node-config" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; height: 200px; font-family: monospace; resize: vertical;">\${node.config}</textarea>
                        </div>
                        
                        <div style="text-align: right; margin-top: 20px;">
                            <button onclick="closeEditModal()" class="btn" style="margin-right: 10px; background: #6c757d; color: white;">取消</button>
                            <button onclick="saveEditedNode('\${node.id}')" class="btn btn-success">保存</button>
                        </div>
                    </div>
                </div>
            \`;
            
            // 添加到页面
            document.body.insertAdjacentHTML('beforeend', modalHtml);
        }

        // 关闭编辑模态框
        function closeEditModal() {
            const modal = document.getElementById('editModal');
            if (modal) {
                modal.remove();
            }
        }

        // 保存编辑的节点
        async function saveEditedNode(nodeId) {
            const name = document.getElementById('edit-node-name').value.trim();
            const config = document.getElementById('edit-node-config').value.trim();
            
            if (!name || !config) {
                showStatus('节点名称和配置不能为空', 'error');
                return;
            }
            
            try {
                const response = await fetch('/api/custom-nodes/' + nodeId, {
                    method: 'PUT',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ name, config })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('节点更新成功', 'success');
                    closeEditModal();
                    loadCustomNodes(); // 重新加载节点列表
                } else {
                    showStatus(result.error || '更新失败', 'error');
                    console.error('Update node error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 复制机场订阅
        async function copySubscription(subscriptionId) {
            try {
                // 获取订阅数据
                const response = await fetch('/api/subscriptions');
                const subscriptions = await response.json();
                const subscription = subscriptions.find(s => s.id === subscriptionId);
                
                if (!subscription) {
                    showStatus('未找到要复制的订阅', 'error');
                    return;
                }
                
                // 复制到剪贴板
                await navigator.clipboard.writeText(subscription.url);
                showStatus('订阅链接已复制到剪贴板', 'success');
            } catch (error) {
                // 如果剪贴板API不可用,使用传统方法
                try {
                    const response = await fetch('/api/subscriptions');
                    const subscriptions = await response.json();
                    const subscription = subscriptions.find(s => s.id === subscriptionId);
                    
                    if (subscription) {
                        // 创建临时文本区域
                        const textArea = document.createElement('textarea');
                        textArea.value = subscription.url;
                        document.body.appendChild(textArea);
                        textArea.select();
                        document.execCommand('copy');
                        document.body.removeChild(textArea);
                        showStatus('订阅链接已复制到剪贴板', 'success');
                    } else {
                        showStatus('未找到要复制的订阅', 'error');
                    }
                } catch (fallbackError) {
                    showStatus('复制失败: ' + error.message, 'error');
                    console.error('Copy subscription error:', error);
                }
            }
        }

        // 编辑机场订阅
        async function editSubscription(subscriptionId) {
            try {
                // 获取订阅数据
                const response = await fetch('/api/subscriptions');
                const subscriptions = await response.json();
                const subscription = subscriptions.find(s => s.id === subscriptionId);
                
                if (!subscription) {
                    showStatus('未找到要编辑的订阅', 'error');
                    return;
                }
                
                // 显示编辑对话框
                showSubscriptionEditModal(subscription);
            } catch (error) {
                showStatus('获取订阅信息失败: ' + error.message, 'error');
                console.error('Get subscription error:', error);
            }
        }

        // 显示订阅编辑模态框
        function showSubscriptionEditModal(subscription) {
            // 创建模态框HTML
            const modalHtml = \`
                <div id="subscriptionEditModal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; display: flex; align-items: center; justify-content: center;">
                    <div style="background: white; padding: 30px; border-radius: 8px; width: 90%; max-width: 600px; max-height: 80vh; overflow-y: auto;">
                        <h3 style="margin-bottom: 20px; color: #333;">编辑机场订阅</h3>
                        
                        <div class="form-group">
                            <label>订阅名称</label>
                            <input type="text" id="edit-subscription-name" value="\${subscription.name}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 15px;">
                        </div>
                        
                        <div class="form-group">
                            <label>订阅链接</label>
                            <textarea id="edit-subscription-url" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; height: 120px; font-family: monospace; resize: vertical;">\${subscription.url}</textarea>
                        </div>
                        
                        <div style="text-align: right; margin-top: 20px;">
                            <button onclick="closeSubscriptionEditModal()" class="btn" style="margin-right: 10px; background: #6c757d; color: white;">取消</button>
                            <button onclick="saveEditedSubscription('\${subscription.id}')" class="btn btn-success">保存</button>
                        </div>
                    </div>
                </div>
            \`;
            
            // 添加到页面
            document.body.insertAdjacentHTML('beforeend', modalHtml);
        }

        // 关闭订阅编辑模态框
        function closeSubscriptionEditModal() {
            const modal = document.getElementById('subscriptionEditModal');
            if (modal) {
                modal.remove();
            }
        }

        // 保存编辑的订阅
        async function saveEditedSubscription(subscriptionId) {
            const name = document.getElementById('edit-subscription-name').value.trim();
            const url = document.getElementById('edit-subscription-url').value.trim();
            
            if (!name || !url) {
                showStatus('订阅名称和链接不能为空', 'error');
                return;
            }
            
            // 验证URL格式
            try {
                new URL(url);
            } catch (urlError) {
                showStatus('订阅链接格式不正确', 'error');
                return;
            }
            
            try {
                const response = await fetch('/api/subscriptions/' + subscriptionId, {
                    method: 'PUT',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ name, url })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('订阅更新成功', 'success');
                    closeSubscriptionEditModal();
                    loadSubscriptions(); // 重新加载订阅列表
                } else {
                    showStatus(result.error || '更新失败', 'error');
                    console.error('Update subscription error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 全选/取消全选订阅功能
        function toggleSelectAllSub() {
            const selectAllCheckbox = document.getElementById('select-all-sub-checkbox');
            const subCheckboxes = document.querySelectorAll('.sub-checkbox-input');
            
            // 检查是否所有订阅都被选中
            const allChecked = Array.from(subCheckboxes).every(checkbox => checkbox.checked);
            
            // 如果全部选中,则取消全选;否则全选
            const shouldCheck = !allChecked;
            
            subCheckboxes.forEach(checkbox => {
                checkbox.checked = shouldCheck;
            });
            
            // 更新全选复选框状态
            selectAllCheckbox.checked = shouldCheck;
            selectAllCheckbox.indeterminate = false;
            
            updateBatchSubButton();
        }

        // 处理全选订阅复选框变化
        function handleSelectAllSubChange() {
            const selectAllCheckbox = document.getElementById('select-all-sub-checkbox');
            const subCheckboxes = document.querySelectorAll('.sub-checkbox-input');
            
            // 根据全选复选框的状态来设置所有订阅复选框
            const isChecked = selectAllCheckbox.checked;
            subCheckboxes.forEach(checkbox => {
                checkbox.checked = isChecked;
            });
            
            // 清除indeterminate状态
            selectAllCheckbox.indeterminate = false;
            
            updateBatchSubButton();
        }

        // 处理单个订阅复选框变化
        function handleSubCheckboxChange() {
            const selectAllCheckbox = document.getElementById('select-all-sub-checkbox');
            const subCheckboxes = document.querySelectorAll('.sub-checkbox-input');
            
            // 检查是否所有订阅都被选中
            const allChecked = Array.from(subCheckboxes).every(checkbox => checkbox.checked);
            const someChecked = Array.from(subCheckboxes).some(checkbox => checkbox.checked);
            
            selectAllCheckbox.checked = allChecked;
            selectAllCheckbox.indeterminate = someChecked && !allChecked;
            
            updateBatchSubButton();
        }

        // 更新批量操作订阅按钮状态
        function updateBatchSubButton() {
            const selectedCheckboxes = document.querySelectorAll('.sub-checkbox-input:checked');
            const batchDeleteBtn = document.getElementById('batch-delete-sub-btn');
            const batchPauseBtn = document.getElementById('batch-pause-sub-btn');
            const batchEnableBtn = document.getElementById('batch-enable-sub-btn');
            
            if (selectedCheckboxes.length > 0) {
                batchDeleteBtn.disabled = false;
                batchDeleteBtn.textContent = '批量删除 (' + selectedCheckboxes.length + ')';
                batchPauseBtn.disabled = false;
                batchPauseBtn.textContent = '暂停选中 (' + selectedCheckboxes.length + ')';
                batchEnableBtn.disabled = false;
                batchEnableBtn.textContent = '启用选中 (' + selectedCheckboxes.length + ')';
            } else {
                batchDeleteBtn.disabled = true;
                batchDeleteBtn.textContent = '批量删除';
                batchPauseBtn.disabled = true;
                batchPauseBtn.textContent = '暂停选中';
                batchEnableBtn.disabled = true;
                batchEnableBtn.textContent = '启用选中';
            }
        }

        // 批量删除订阅
        async function batchDeleteSubscriptions() {
            const selectedCheckboxes = document.querySelectorAll('.sub-checkbox-input:checked');
            const selectedIds = Array.from(selectedCheckboxes).map(checkbox => checkbox.value);
            
            if (selectedIds.length === 0) {
                showStatus('请先选择要删除的订阅', 'error');
                return;
            }
            
            if (!confirm('确定要删除选中的 ' + selectedIds.length + ' 个订阅吗?')) {
                return;
            }
            
            try {
                // 批量删除请求
                const response = await fetch('/api/subscriptions/batch-delete', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ ids: selectedIds })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('成功删除 ' + result.deletedCount + ' 个订阅', 'success');
                    loadSubscriptions(); // 重新加载订阅列表
                } else {
                    showStatus(result.error || '批量删除失败', 'error');
                    console.error('Batch delete subscriptions error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 批量暂停订阅
        async function batchPauseSubscriptions() {
            const selectedCheckboxes = document.querySelectorAll('.sub-checkbox-input:checked');
            const selectedIds = Array.from(selectedCheckboxes).map(checkbox => checkbox.value);
            
            if (selectedIds.length === 0) {
                showStatus('请先选择要暂停的订阅', 'error');
                return;
            }
            
            if (!confirm('确定要暂停选中的 ' + selectedIds.length + ' 个订阅吗?暂停的订阅将不会出现在订阅中。')) {
                return;
            }
            
            try {
                const response = await fetch('/api/subscriptions/batch-toggle', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ ids: selectedIds, enabled: false })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('成功暂停 ' + result.updatedCount + ' 个订阅', 'success');
                    loadSubscriptions(); // 重新加载订阅列表
                } else {
                    showStatus(result.error || '批量暂停失败', 'error');
                    console.error('Batch pause subscriptions error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 批量启用订阅
        async function batchEnableSubscriptions() {
            const selectedCheckboxes = document.querySelectorAll('.sub-checkbox-input:checked');
            const selectedIds = Array.from(selectedCheckboxes).map(checkbox => checkbox.value);
            
            if (selectedIds.length === 0) {
                showStatus('请先选择要启用的订阅', 'error');
                return;
            }
            
            if (!confirm('确定要启用选中的 ' + selectedIds.length + ' 个订阅吗?')) {
                return;
            }
            
            try {
                const response = await fetch('/api/subscriptions/batch-toggle', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ ids: selectedIds, enabled: true })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    showStatus('成功启用 ' + result.updatedCount + ' 个订阅', 'success');
                    loadSubscriptions(); // 重新加载订阅列表
                } else {
                    showStatus(result.error || '批量启用失败', 'error');
                    console.error('Batch enable subscriptions error:', result);
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Network error:', error);
            }
        }

        // 登出功能
        async function logout() {
            if (!confirm('确定要登出吗?')) return;
            
            try {
                const response = await fetch('${LOGIN_PATH}/logout', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    // 登出成功,跳转到登录页面
                    window.location.href = '${LOGIN_PATH}';
                } else {
                    showStatus('登出失败: ' + (result.error || '未知错误'), 'error');
                }
            } catch (error) {
                showStatus('网络错误: ' + error.message, 'error');
                console.error('Logout error:', error);
            }
        }

        // 页面加载时初始化
        loadData();
        checkStorageStatus();
    </script>
</body>
</html>`;
  
  return new Response(html, {
    headers: { 'Content-Type': 'text/html; charset=utf-8' }
  });
}

// 登录页面处理函数
async function handleLoginPage(request) {
  const html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节点管理后台 - 登录</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔐</text></svg>">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .login-container {
            background: white;
            padding: 40px;
            border-radius: 12px;
            box-shadow: 0 15px 35px rgba(0,0,0,0.1);
            width: 100%;
            max-width: 400px;
        }
        .login-header {
            text-align: center;
            margin-bottom: 30px;
        }
        .login-header h1 {
            color: #333;
            margin-bottom: 10px;
            font-size: 28px;
        }
        .login-header p {
            color: #666;
            font-size: 14px;
        }
        .form-group {
            margin-bottom: 20px;
        }
        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
            color: #333;
        }
        .form-group input {
            width: 100%;
            padding: 12px 16px;
            border: 2px solid #e1e5e9;
            border-radius: 8px;
            font-size: 16px;
            transition: border-color 0.3s ease;
        }
        .form-group input:focus {
            outline: none;
            border-color: #667eea;
        }
        .login-btn {
            width: 100%;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            padding: 12px;
            border-radius: 8px;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            transition: transform 0.2s ease;
        }
        .login-btn:hover {
            transform: translateY(-2px);
        }
        .login-btn:disabled {
            opacity: 0.6;
            cursor: not-allowed;
            transform: none;
        }
        .error-message {
            background: #fee;
            color: #c33;
            padding: 10px;
            border-radius: 6px;
            margin-bottom: 20px;
            font-size: 14px;
            display: none;
        }
        .loading {
            display: none;
            text-align: center;
            margin-top: 10px;
        }
        .spinner {
            border: 2px solid #f3f3f3;
            border-top: 2px solid #667eea;
            border-radius: 50%;
            width: 20px;
            height: 20px;
            animation: spin 1s linear infinite;
            margin: 0 auto;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="login-container">
        <div class="login-header">
            <h1>🔐 登录管理后台</h1>
            <p>请输入您的登录凭据</p>
        </div>
        
        <div class="error-message" id="errorMessage"></div>
        
        <form id="loginForm">
            <div class="form-group">
                <label for="username">用户名</label>
                <input type="text" id="username" name="username" required autocomplete="username">
            </div>
            
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" id="password" name="password" required autocomplete="current-password">
            </div>
            
            <button type="submit" class="login-btn" id="loginBtn">
                登录
            </button>
        </form>
        
        <div class="loading" id="loading">
            <div class="spinner"></div>
            <p>正在验证...</p>
        </div>
    </div>

    <script>
        document.getElementById('loginForm').addEventListener('submit', async function(e) {
            e.preventDefault();
            
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            const errorDiv = document.getElementById('errorMessage');
            const loginBtn = document.getElementById('loginBtn');
            const loading = document.getElementById('loading');
            
            // 隐藏错误信息
            errorDiv.style.display = 'none';
            
            // 显示加载状态
            loginBtn.disabled = true;
            loading.style.display = 'block';
            
            try {
                const response = await fetch('${LOGIN_PATH}/auth', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ username, password })
                });
                
                const result = await response.json();
                
                if (response.ok && result.success) {
                    // 登录成功,跳转到管理页面
                    window.location.href = '${ADMIN_PATH}';
                } else {
                    // 显示错误信息
                    errorDiv.textContent = result.error || '登录失败,请检查用户名和密码';
                    errorDiv.style.display = 'block';
                }
            } catch (error) {
                errorDiv.textContent = '网络错误,请稍后重试';
                errorDiv.style.display = 'block';
            } finally {
                // 隐藏加载状态
                loginBtn.disabled = false;
                loading.style.display = 'none';
            }
        });
        
        // 自动聚焦到用户名输入框
        document.getElementById('username').focus();
    </script>
</body>
</html>`;
  
  return new Response(html, {
    headers: { 'Content-Type': 'text/html; charset=utf-8' }
  });
}

// 登录认证API
async function handleLoginAuth(request) {
  try {
    const { username, password } = await request.json();
    
    // 验证用户名和密码
    if (username === ENV_USERNAME && password === ENV_PASSWORD) {
      // 生成会话ID
      const sessionId = generateSessionId();
      const sessionData = {
        username: username,
        loginTime: Date.now(),
        expires: Date.now() + (24 * 60 * 60 * 1000) // 24小时过期
      };
      
      // 存储会话到KV
      await NODES_KV.put(`session_${sessionId}`, JSON.stringify(sessionData));
      
      // 设置Cookie
      const response = new Response(JSON.stringify({ 
        success: true, 
        message: '登录成功' 
      }), {
        headers: { 
          'Content-Type': 'application/json',
          'Set-Cookie': `session=${sessionId}; Path=/; HttpOnly; Max-Age=86400; SameSite=Strict`
        }
      });
      
      return response;
    } else {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '用户名或密码错误' 
      }), {
        status: 401,
        headers: { 'Content-Type': 'application/json' }
      });
    }
  } catch (error) {
    return new Response(JSON.stringify({ 
      success: false, 
      error: '登录验证失败' 
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 登出API
async function handleLogout(request) {
  try {
    const sessionId = getSessionIdFromRequest(request);
    
    if (sessionId) {
      // 删除会话
      await NODES_KV.delete(`session_${sessionId}`);
    }
    
    return new Response(JSON.stringify({ 
      success: true, 
      message: '登出成功' 
    }), {
      headers: { 
        'Content-Type': 'application/json',
        'Set-Cookie': 'session=; Path=/; HttpOnly; Max-Age=0; SameSite=Strict'
      }
    });
  } catch (error) {
    return new Response(JSON.stringify({ 
      success: false, 
      error: '登出失败' 
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 生成会话ID
function generateSessionId() {
  return 'sess_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}

// 从请求中获取会话ID
function getSessionIdFromRequest(request) {
  const cookieHeader = request.headers.get('Cookie');
  if (!cookieHeader) return null;
  
  const cookies = cookieHeader.split(';').map(c => c.trim());
  const sessionCookie = cookies.find(c => c.startsWith('session='));
  
  if (sessionCookie) {
    return sessionCookie.split('=')[1];
  }
  
  return null;
}

// 验证会话
async function validateSession(request) {
  try {
    const sessionId = getSessionIdFromRequest(request);
    
    if (!sessionId) {
      return { valid: false, reason: 'No session' };
    }
    
    const sessionData = await NODES_KV.get(`session_${sessionId}`);
    
    if (!sessionData) {
      return { valid: false, reason: 'Session not found' };
    }
    
    const session = JSON.parse(sessionData);
    
    // 检查会话是否过期
    if (Date.now() > session.expires) {
      // 删除过期会话
      await NODES_KV.delete(`session_${sessionId}`);
      return { valid: false, reason: 'Session expired' };
    }
    
    return { valid: true, session: session };
  } catch (error) {
    return { valid: false, reason: 'Session validation error' };
  }
}

// 带认证的管理页面处理函数
async function handleAdminPageWithAuth(request) {
  // 验证登录状态
  const sessionValidation = await validateSession(request);
  
  if (!sessionValidation.valid) {
    // 未登录,重定向到登录页面
    return new Response(null, {
      status: 302,
      headers: {
        'Location': LOGIN_PATH
      }
    });
  }
  
  // 已登录,显示管理页面
  return handleAdminPage(request);
}

// 带认证的API处理函数
async function handleAPIWithAuth(request) {
  // 验证登录状态
  const sessionValidation = await validateSession(request);
  
  if (!sessionValidation.valid) {
    return new Response(JSON.stringify({ 
      success: false, 
      error: '未登录或会话已过期,请重新登录' 
    }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  
  // 已登录,处理API请求
  return handleAPI(request);
}

// API处理函数
async function handleAPI(request) {
  const url = new URL(request.url);
  const pathname = url.pathname;
  const method = request.method;

  // 自建节点API
  if (pathname === '/api/custom-nodes') {
    if (method === 'GET') {
      return getCustomNodes();
    } else if (method === 'POST') {
      const data = await request.json();
      return addCustomNode(data);
    }
  }

  // 删除自建节点API
  if (pathname.startsWith('/api/custom-nodes/') && method === 'DELETE') {
    const id = pathname.split('/')[3];
    return deleteCustomNode(id);
  }

  // 更新自建节点API
  if (pathname.startsWith('/api/custom-nodes/') && method === 'PUT') {
    const id = pathname.split('/')[3];
    const data = await request.json();
    return updateCustomNode(id, data);
  }

  // 批量删除自建节点API
  if (pathname === '/api/custom-nodes/batch-delete' && method === 'POST') {
    const data = await request.json();
    return batchDeleteCustomNodes(data);
  }

  // 批量暂停/启用自建节点API
  if (pathname === '/api/custom-nodes/batch-toggle' && method === 'POST') {
    const data = await request.json();
    return batchToggleCustomNodes(data);
  }

  // 机场订阅API
  if (pathname === '/api/subscriptions') {
    if (method === 'GET') {
      return getSubscriptions();
    } else if (method === 'POST') {
      const data = await request.json();
      return addSubscription(data);
    }
  }

  // 删除机场订阅API
  if (pathname.startsWith('/api/subscriptions/') && method === 'DELETE') {
    const id = pathname.split('/')[3];
    return deleteSubscription(id);
  }

  // 更新机场订阅API
  if (pathname.startsWith('/api/subscriptions/') && method === 'PUT') {
    const id = pathname.split('/')[3];
    const data = await request.json();
    return updateSubscription(id, data);
  }

  // 批量删除机场订阅API
  if (pathname === '/api/subscriptions/batch-delete' && method === 'POST') {
    const data = await request.json();
    return batchDeleteSubscriptions(data);
  }

  // 批量暂停/启用机场订阅API
  if (pathname === '/api/subscriptions/batch-toggle' && method === 'POST') {
    const data = await request.json();
    return batchToggleSubscriptions(data);
  }

  // 存储状态检查API
  if (pathname === '/api/storage-status') {
    return checkStorageStatus();
  }

  // KV测试API
  if (pathname === '/api/kv-test') {
    return testKVConnection();
  }

  // 节点名称解码测试API
  if (pathname === '/api/decode-test') {
    return testNodeNameDecoding();
  }

  // Base64解码测试API
  if (pathname === '/api/base64-test') {
    return testBase64Decoding();
  }

  return new Response('Not Found', { status: 404 });
}

// 检查存储状态
async function checkStorageStatus() {
  // 检查KV是否被正确绑定
  let usingKV = false;
  let storageType = '内存存储';
  let message = '数据在Worker重启后会丢失';
  
  // 检查是否使用了真实的KV存储
  if (NODES_KV !== fallbackStorage) {
    usingKV = true;
    storageType = 'KV存储';
    message = '数据将持久保存';
  }
  
  return new Response(JSON.stringify({ 
    usingKV: usingKV,
    storageType: storageType,
    message: message,
    debug: {
      hasNODES_KV: typeof NODES_KV !== 'undefined',
      isFallbackStorage: NODES_KV === fallbackStorage,
      hasNODES_KV_BINDING: typeof NODES_KV_BINDING !== 'undefined',
      NODES_KV_type: typeof NODES_KV
    }
  }), {
    headers: { 'Content-Type': 'application/json' }
  });
}

// 测试KV连接
async function testKVConnection() {
  const testKey = 'kv_test_' + Date.now();
  const testValue = 'test_value_' + Math.random();
  
  try {
    // 尝试写入测试数据
    await NODES_KV.put(testKey, testValue);
    
    // 尝试读取测试数据
    const retrievedValue = await NODES_KV.get(testKey);
    
    // 清理测试数据
    try {
      await NODES_KV.delete(testKey);
    } catch (deleteError) {
      console.log('清理测试数据失败:', deleteError);
    }
    
    const isKVWorking = retrievedValue === testValue;
    
    return new Response(JSON.stringify({
      success: true,
      kvWorking: isKVWorking,
      testKey: testKey,
      testValue: testValue,
      retrievedValue: retrievedValue,
      storageType: isKVWorking ? 'KV存储' : '内存存储',
      message: isKVWorking ? 'KV存储工作正常' : 'KV存储未正确配置',
      debug: {
        NODES_KV_type: typeof NODES_KV,
        NODES_KV_constructor: NODES_KV?.constructor?.name,
        hasGet: typeof NODES_KV?.get === 'function',
        hasPut: typeof NODES_KV?.put === 'function',
        hasDelete: typeof NODES_KV?.delete === 'function'
      }
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    return new Response(JSON.stringify({
      success: false,
      error: error.message,
      kvWorking: false,
      storageType: '内存存储',
      message: 'KV测试失败: ' + error.message,
      debug: {
        NODES_KV_type: typeof NODES_KV,
        error_stack: error.stack
      }
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 测试节点名称解码
async function testNodeNameDecoding() {
  const testConfig = 'vless://[email protected]:1443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=swdist.apple.com&fp=qq&pbk=ZIBYUH_qQSeI1T6xImXG6MEZXP2yZW3NqGa8W69Cfyk&sid=dde50f55d81116&spx=%2F&type=tcp&headerType=none#%E6%82%89%E5%B0%BC%E5%A4%A7%E9%99%86%E4%BC%98%E5%8C%96BGP%E7%BA%BF%E8%B7%AF';
  
  let nodeName = '';
  if (testConfig.includes('#')) {
    const namePart = testConfig.split('#').pop().trim();
    try {
      nodeName = decodeURIComponent(namePart);
    } catch (e) {
      nodeName = namePart;
    }
  }
  
  return new Response(JSON.stringify({
    success: true,
    originalConfig: testConfig,
    encodedName: testConfig.split('#').pop(),
    decodedName: nodeName,
    testResult: nodeName === '悉尼大陆优化BGP线路'
  }), {
    headers: { 'Content-Type': 'application/json' }
  });
}

// 测试Base64解码
async function testBase64Decoding() {
  const testBase64 = 'aHlzdGVyaWEyOi8vNzljNGZlMTEtOTc4Ny00MDZiLWJmOTQtYzFjMWRiZjU5ZTI4QDc3LjIyMy4yMTQuMTkzOjMxNDY4P3NuaT13d3cuYmluZy5jb20maW5zZWN1cmU9MSNpbG92ZXlvdSUyMC0lMjAlRjAlOUYlOTIlOEUlREElQTklRDglQTclRDklODYlRDklODElREIlOEMlREElQUYlMjAlRDklODclRDglQTclREIlOEMlMjAlRDglQTglREIlOEMlRDglQjQlRDglQUElRDglQjElMjAlRDglQUYlRDglQjElMjAlREElODYlRDklODYlRDklODQlMjAlRDglQUElRDklODQlREElQUYlRDglQjElRDglQTcuLi4NCg==';
  
  try {
    const decodedConfig = atob(testBase64);
    console.log('Base64解码测试:', decodedConfig);
    
    // 解析解码后的配置
    const lines = decodedConfig.split('\n').map(line => line.trim()).filter(line => line);
    const nodes = [];
    
    for (const line of lines) {
      if (line) {
        const node = processNodeConfig(line, [], nodes);
        if (node) {
          nodes.push(node);
        }
      }
    }
    
    return new Response(JSON.stringify({
      success: true,
      originalBase64: testBase64,
      decodedConfig: decodedConfig,
      parsedNodes: nodes,
      nodeCount: nodes.length,
      isBase64Detected: isBase64Encoded(testBase64)
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    return new Response(JSON.stringify({
      success: false,
      error: error.message,
      originalBase64: testBase64,
      isBase64Detected: isBase64Encoded(testBase64)
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 获取自建节点
async function getCustomNodes() {
  try {
    const data = await NODES_KV.get(KV_KEYS.CUSTOM_NODES);
    const nodes = data ? JSON.parse(data) : [];
    return new Response(JSON.stringify(nodes), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    return new Response(JSON.stringify([]), {
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 添加自建节点
async function addCustomNode(data) {
  try {
    console.log('Adding custom nodes:', data);
    
    // 验证输入数据
    if (!data.config) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '节点配置不能为空' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    const existingData = await NODES_KV.get(KV_KEYS.CUSTOM_NODES);
    console.log('Existing data:', existingData);
    
    const nodes = existingData ? JSON.parse(existingData) : [];
    console.log('Current nodes count:', nodes.length);
    
    // 解析多个节点配置
    const configLines = data.config.split('\n').map(line => line.trim()).filter(line => line);
    const newNodes = [];
    const duplicateNodes = [];
    
    for (let i = 0; i < configLines.length; i++) {
      let config = configLines[i];
      
      // 检测并解码Base64编码的节点配置
      if (isBase64Encoded(config)) {
        try {
          const decodedConfig = atob(config);
          console.log('Base64解码前:', config);
          console.log('Base64解码后:', decodedConfig);
          
          // 如果解码后包含多个节点(用换行分隔),分别处理
          const decodedLines = decodedConfig.split('\n').map(line => line.trim()).filter(line => line);
          for (const decodedLine of decodedLines) {
            if (decodedLine) {
              const node = processNodeConfig(decodedLine, nodes, newNodes);
              if (node) {
                newNodes.push(node);
              } else {
                // 记录重复的节点
                duplicateNodes.push(decodedLine);
              }
            }
          }
          continue; // 跳过下面的单个节点处理
        } catch (error) {
          console.error('Base64解码失败:', error);
          // 如果解码失败,继续按普通配置处理
        }
      }
      
      // 处理普通节点配置
      const node = processNodeConfig(config, nodes, newNodes);
      if (node) {
        newNodes.push(node);
      } else {
        // 记录重复的节点
        duplicateNodes.push(config);
      }
    }
    
    // 添加新节点到现有列表
    nodes.push(...newNodes);
    console.log('New nodes count:', nodes.length);
    console.log('Added nodes:', newNodes.length);
    console.log('Duplicate nodes:', duplicateNodes.length);
    
    const putResult = await NODES_KV.put(KV_KEYS.CUSTOM_NODES, JSON.stringify(nodes));
    console.log('Put result:', putResult);
    
    // 构建响应消息
    let message = '';
    if (newNodes.length > 0 && duplicateNodes.length > 0) {
      message = `成功添加 ${newNodes.length} 个节点,跳过 ${duplicateNodes.length} 个重复节点`;
    } else if (newNodes.length > 0) {
      message = `成功添加 ${newNodes.length} 个节点`;
    } else if (duplicateNodes.length > 0) {
      message = `所有 ${duplicateNodes.length} 个节点都已存在,未添加任何新节点`;
    } else {
      message = '没有有效的节点配置';
    }
    
    return new Response(JSON.stringify({ 
      success: true, 
      addedCount: newNodes.length,
      duplicateCount: duplicateNodes.length,
      message: message,
      duplicates: duplicateNodes
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Add custom node error:', error);
    return new Response(JSON.stringify({ 
      success: false, 
      error: error.message,
      details: '添加节点时发生错误'
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 检测是否为Base64编码
function isBase64Encoded(str) {
  // Base64字符串通常只包含A-Z, a-z, 0-9, +, /, = 字符
  const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
  // 长度必须是4的倍数
  return base64Regex.test(str) && str.length % 4 === 0 && str.length > 20;
}

// 检测节点是否重复
function isNodeDuplicate(config, existingNodes) {
  // 标准化配置字符串进行比较
  const normalizeConfig = (config) => {
    // 移除可能的空白字符和换行符
    return config.trim().replace(/\s+/g, '');
  };
  
  const normalizedNewConfig = normalizeConfig(config);
  
  // 检查是否与现有节点重复
  return existingNodes.some(node => {
    const normalizedExistingConfig = normalizeConfig(node.config);
    return normalizedExistingConfig === normalizedNewConfig;
  });
}

// 处理单个节点配置
function processNodeConfig(config, existingNodes, newNodes) {
  // 检查是否重复
  if (isNodeDuplicate(config, existingNodes)) {
    console.log('Duplicate node detected:', config);
    return null; // 返回null表示跳过重复节点
  }
  
  // 提取节点名称(从#后面或配置中提取)
  let nodeName = '';
  if (config.includes('#')) {
    const namePart = config.split('#').pop().trim();
    // 解码URL编码的中文字符
    try {
      nodeName = decodeURIComponent(namePart);
    } catch (e) {
      nodeName = namePart; // 如果解码失败,使用原始字符串
    }
  } else if (config.includes('ps=')) {
    // 对于vmess链接,尝试从ps参数提取名称
    const psMatch = config.match(/ps=([^&]+)/);
    if (psMatch) {
      try {
        nodeName = decodeURIComponent(psMatch[1]);
      } catch (e) {
        nodeName = psMatch[1]; // 如果解码失败,使用原始字符串
      }
    }
  } else if (config.includes('remarks=')) {
    // 对于其他协议,尝试从remarks参数提取名称
    const remarksMatch = config.match(/remarks=([^&]+)/);
    if (remarksMatch) {
      try {
        nodeName = decodeURIComponent(remarksMatch[1]);
      } catch (e) {
        nodeName = remarksMatch[1]; // 如果解码失败,使用原始字符串
      }
    }
  }
  
  // 如果没有提取到名称,使用默认名称
  if (!nodeName) {
    nodeName = `节点 ${existingNodes.length + newNodes.length + 1}`;
  }
  
  const newNode = {
    id: (Date.now() + Math.random()).toString(),
    name: nodeName,
    config: config,
    enabled: true, // 默认启用
    createdAt: new Date().toISOString()
  };
  
  return newNode;
}

// 删除自建节点
async function deleteCustomNode(id) {
  try {
    console.log('Deleting custom node:', id);
    
    if (!id) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '节点ID不能为空' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    const existingData = await NODES_KV.get(KV_KEYS.CUSTOM_NODES);
    const nodes = existingData ? JSON.parse(existingData) : [];
    
    console.log('Current nodes count:', nodes.length);
    
    const originalLength = nodes.length;
    const filteredNodes = nodes.filter(node => node.id !== id);
    
    if (filteredNodes.length === originalLength) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '未找到要删除的节点' 
      }), {
        status: 404,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    console.log('Filtered nodes count:', filteredNodes.length);
    
    await NODES_KV.put(KV_KEYS.CUSTOM_NODES, JSON.stringify(filteredNodes));
    
    return new Response(JSON.stringify({ 
      success: true, 
      message: '节点删除成功' 
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Delete custom node error:', error);
    return new Response(JSON.stringify({ 
      success: false, 
      error: error.message,
      details: '删除节点时发生错误'
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 更新自建节点
async function updateCustomNode(id, data) {
  try {
    console.log('Updating custom node:', id, data);
    
    // 验证输入数据
    if (!data.name || !data.config) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '节点名称和配置不能为空' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    const existingData = await NODES_KV.get(KV_KEYS.CUSTOM_NODES);
    const nodes = existingData ? JSON.parse(existingData) : [];
    
    console.log('Current nodes count:', nodes.length);
    
    // 查找要更新的节点
    const nodeIndex = nodes.findIndex(node => node.id === id);
    
    if (nodeIndex === -1) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '未找到要更新的节点' 
      }), {
        status: 404,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    // 更新节点信息
    nodes[nodeIndex].name = data.name;
    nodes[nodeIndex].config = data.config;
    nodes[nodeIndex].updatedAt = new Date().toISOString();
    
    console.log('Updated node:', nodes[nodeIndex]);
    
    await NODES_KV.put(KV_KEYS.CUSTOM_NODES, JSON.stringify(nodes));
    
    return new Response(JSON.stringify({ 
      success: true, 
      message: '节点更新成功'
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Update custom node error:', error);
    return new Response(JSON.stringify({ 
      success: false, 
      error: error.message,
      details: '更新节点时发生错误'
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 批量删除自建节点
async function batchDeleteCustomNodes(data) {
  try {
    console.log('Batch deleting custom nodes:', data);
    
    // 验证输入数据
    if (!data.ids || !Array.isArray(data.ids) || data.ids.length === 0) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '节点ID列表不能为空' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    const existingData = await NODES_KV.get(KV_KEYS.CUSTOM_NODES);
    const nodes = existingData ? JSON.parse(existingData) : [];
    
    console.log('Current nodes count:', nodes.length);
    console.log('Nodes to delete:', data.ids);
    
    const originalLength = nodes.length;
    const filteredNodes = nodes.filter(node => !data.ids.includes(node.id));
    const deletedCount = originalLength - filteredNodes.length;
    
    console.log('Filtered nodes count:', filteredNodes.length);
    console.log('Deleted count:', deletedCount);
    
    if (deletedCount === 0) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '未找到要删除的节点' 
      }), {
        status: 404,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    await NODES_KV.put(KV_KEYS.CUSTOM_NODES, JSON.stringify(filteredNodes));
    
    return new Response(JSON.stringify({ 
      success: true, 
      deletedCount: deletedCount,
      message: `成功删除 ${deletedCount} 个节点`
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Batch delete custom nodes error:', error);
    return new Response(JSON.stringify({ 
      success: false, 
      error: error.message,
      details: '批量删除节点时发生错误'
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 批量暂停/启用自建节点
async function batchToggleCustomNodes(data) {
  try {
    console.log('Batch toggling custom nodes:', data);
    
    // 验证输入数据
    if (!data.ids || !Array.isArray(data.ids) || data.ids.length === 0) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '节点ID列表不能为空' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    if (data.enabled === undefined) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '必须指定enabled状态(true或false)' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    const existingData = await NODES_KV.get(KV_KEYS.CUSTOM_NODES);
    const nodes = existingData ? JSON.parse(existingData) : [];
    
    console.log('Current nodes count:', nodes.length);
    console.log('Nodes to toggle:', data.ids);
    console.log('New enabled state:', data.enabled);
    
    let updatedCount = 0;
    
    // 更新选中节点的enabled状态
    nodes.forEach(node => {
      if (data.ids.includes(node.id)) {
        node.enabled = data.enabled;
        node.updatedAt = new Date().toISOString();
        updatedCount++;
      }
    });
    
    console.log('Updated count:', updatedCount);
    
    if (updatedCount === 0) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '未找到要更新的节点' 
      }), {
        status: 404,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    await NODES_KV.put(KV_KEYS.CUSTOM_NODES, JSON.stringify(nodes));
    
    const action = data.enabled ? '启用' : '暂停';
    return new Response(JSON.stringify({ 
      success: true, 
      updatedCount: updatedCount,
      message: `成功${action} ${updatedCount} 个节点`
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Batch toggle custom nodes error:', error);
    return new Response(JSON.stringify({ 
      success: false, 
      error: error.message,
      details: '批量更新节点状态时发生错误'
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 获取机场订阅
async function getSubscriptions() {
  try {
    const data = await NODES_KV.get(KV_KEYS.SUBSCRIPTION_URLS);
    const subscriptions = data ? JSON.parse(data) : [];
    return new Response(JSON.stringify(subscriptions), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    return new Response(JSON.stringify([]), {
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 添加机场订阅
async function addSubscription(data) {
  try {
    console.log('Adding subscription:', data);
    
    // 验证输入数据
    if (!data.name || !data.url) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '订阅名称和链接不能为空' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    // 验证URL格式
    try {
      new URL(data.url);
    } catch (urlError) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '订阅链接格式不正确' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    const existingData = await NODES_KV.get(KV_KEYS.SUBSCRIPTION_URLS);
    console.log('Existing subscriptions:', existingData);
    
    const subscriptions = existingData ? JSON.parse(existingData) : [];
    console.log('Current subscriptions count:', subscriptions.length);
    
    const newSubscription = {
      id: Date.now().toString(),
      name: data.name,
      url: data.url,
      enabled: true, // 默认启用
      createdAt: new Date().toISOString()
    };
    
    subscriptions.push(newSubscription);
    console.log('New subscriptions count:', subscriptions.length);
    
    const putResult = await NODES_KV.put(KV_KEYS.SUBSCRIPTION_URLS, JSON.stringify(subscriptions));
    console.log('Put result:', putResult);
    
    return new Response(JSON.stringify({ 
      success: true, 
      id: newSubscription.id,
      message: '订阅添加成功'
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Add subscription error:', error);
    return new Response(JSON.stringify({ 
      success: false, 
      error: error.message,
      details: '添加订阅时发生错误'
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// 删除机场订阅
async function deleteSubscription(id) {
  try {
    console.log('Deleting subscription:', id);
    
    if (!id) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '订阅ID不能为空' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    const existingData = await NODES_KV.get(KV_KEYS.SUBSCRIPTION_URLS);
    const subscriptions = existingData ? JSON.parse(existingData) : [];
    
    console.log('Current subscriptions count:', subscriptions.length);
    
    const originalLength = subscriptions.length;
    const filteredSubscriptions = subscriptions.filter(sub => sub.id !== id);
    
    if (filteredSubscriptions.length === originalLength) {
      return new Response(JSON.stringify({ 
        success: false, 
        error: '未找到要删除的订阅' 
      }), {
        status: 404,
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    console.log('Filtered subscriptions count:', filteredSubscriptions.length);
    
    await NODES_KV.put(KV_KEYS.SUBSCRIPTION_URLS, JSON.stringify(filteredSubscriptions));
    
    return new Response(JSON.stringify({ 
      success: true, 
      message: '订阅删除成功' 
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Delete subscription error:', error);
    return new Response(JSON.stringify({ 
      success: false, 
      error: error.message,
      details: '删除订阅时发生错误'
    }), 

留言

暂无留言

0 / 100