@道法自然 #74 大佬,在您的单个添加基础上,实现了批量添加,请您核实,如果可能,麻烦您更新github, realm/web/templates/index.html <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Realm 转发管理面板</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f4f4f4; } h1 { color: #333; text-align: center; } .container { max-width: 800px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } button { padding: 10px 15px; margin: 5px; border: none; border-radius: 5px; background-color: #28a745; color: white; cursor: pointer; transition: background-color 0.3s; } button:hover { background-color: #218838; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { border: 1px solid #ddd; padding: 10px; text-align: left; } th { background-color: #f2f2f2; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; } .form-group input { width: calc(100% - 22px); padding: 10px; border: 1px solid #ccc; border-radius: 5px; } #output { margin-top: 20px; background-color: #fff; padding: 10px; border: 1px solid #ccc; border-radius: 5px; min-height: 50px; white-space: pre-wrap; /* 保持空格和换行 */ } .status-tag { display: inline-flex; align-items: center; padding: 4px 8px; font-size: 14px; line-height: 20px; border-radius: 4px; margin-left: 10px; font-weight: 500; } .status-tag.running { background-color: #52c41a; color: white; } .status-tag.stopped { background-color: #ff4d4f; color: white; } .status-wrapper { display: inline-flex; align-items: center; margin-left: 15px; } .status-label { color: #666; margin-right: 8px; } .button-group { display: flex; align-items: center; margin-bottom: 20px; } </style> </head> <body> <div class="container"> <h1>Realm 转发管理面板</h1> <div class="button-group"> <button id="startButton">启动服务</button> <button id="stopButton">停止服务</button> <button id="logoutButton">登出</button> <div class="status-wrapper"> <span class="status-label">状态:</span> <span id="serviceStatus" class="status-tag">检查中...</span> </div> </div> <div id="output"></div> <h2>当前转发规则</h2> <table id="forwardingTable"> <thead> <tr> <th>序号</th> <th>中转端口</th> <th>落地鸡 IP</th> <th>目标端口</th> <th>操作</th> </tr> </thead> <tbody> <!-- 动态填充转发规则 --> </tbody> </table> <h2>添加转发规则</h2> <div class="form-group"> <label for="localPort">中转端口:</label> <input type="number" id="localPort" required> </div> <div class="form-group"> <label for="remoteIP">落地鸡 IP:</label> <input type="text" id="remoteIP" required> </div> <div class="form-group"> <label for="remotePort">目标端口:</label> <input type="number" id="remotePort" required> </div> <button id="addRuleButton">添加规则</button> <h2>批量添加转发规则</h2> <div class="form-group"> <label for="batchInput">批量添加规则 (格式:localPort:remoteIP:remotePort,每条规则一行):</label> <textarea id="batchInput" rows="5" style="width: 100%;"></textarea> </div> <button id="addBatchButton">批量添加规则</button> </div> <script> async function updateServiceStatus() { try { const response = await fetch(`/check_status`, { method: 'GET' }); if (!response.ok) { throw new Error('检查状态失败:' + response.statusText); } const data = await response.json(); const statusElement = document.getElementById('serviceStatus'); if (data.status === "启用") { statusElement.textContent = "运行中"; statusElement.className = 'status-tag running'; } else { statusElement.textContent = "已停止"; statusElement.className = 'status-tag stopped'; } } catch (error) { console.error('更新状态失败:', error); document.getElementById('serviceStatus').textContent = '未知'; document.getElementById('serviceStatus').className = 'status-tag stopped'; } } // 启动服务 document.getElementById('startButton').onclick = async function() { try { const response = await fetch(`/start_service`, { method: 'POST' }); if (!response.ok) { throw new Error('启动服务失败:' + response.statusText); } const data = await response.json(); document.getElementById('output').innerText = data.message || '服务启动成功'; await updateServiceStatus(); } catch (error) { console.error('启动服务失败:', error); document.getElementById('output').innerText = error.message; } }; // 停止服务 document.getElementById('stopButton').onclick = async function() { try { const response = await fetch(`/stop_service`, { method: 'POST' }); if (!response.ok) { throw new Error('停止服务失败:' + response.statusText); } const data = await response.json(); document.getElementById('output').innerText = data.message || '服务停止成功'; await updateServiceStatus(); } catch (error) { console.error('停止服务失败:', error); document.getElementById('output').innerText = error.message; } }; // 添加转发规则 document.getElementById('addRuleButton').onclick = async function() { const localPort = document.getElementById('localPort').value; const remoteIP = document.getElementById('remoteIP').value; const remotePort = document.getElementById('remotePort').value; try { const response = await fetch(`/add_rule`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ listen: `0.0.0.0:${localPort}`, remote: `${remoteIP}:${remotePort}` }), }); if (!response.ok) { throw new Error('添加规则失败:' + response.statusText); } alert('转发规则添加成功!'); await fetchForwardingRules(); // 刷新规则列表 } catch (error) { console.error('添加规则失败:', error); alert('添加规则失败:' + error.message); } }; // 批量添加转发规则 document.getElementById('addBatchButton').onclick = async function() { const batchInput = document.getElementById('batchInput').value.trim(); const rules = batchInput.split('\n').map(line => { const [localPort, remoteIP, remotePort] = line.split(':'); return { listen: `0.0.0.0:${localPort}`, remote: `${remoteIP}:${remotePort}` }; }); try { // 遍历每个规则并逐个添加 for (const rule of rules) { const response = await fetch(`/add_rule`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(rule), }); if (!response.ok) { throw new Error(`批量添加规则失败:${response.statusText}`); } } alert('批量转发规则添加成功!'); await fetchForwardingRules(); // 刷新规则列表 } catch (error) { console.error('批量添加规则失败:', error); alert('批量添加规则失败:' + error.message); } }; // 删除转发规则 async function deleteRule(listenAddress) { try { const response = await fetch(`/delete_rule?listen=${encodeURIComponent(listenAddress)}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('删除规则失败:' + response.statusText); } alert('转发规则删除成功!'); await fetchForwardingRules(); // 刷新规则列表 } catch (error) { console.error('删除规则失败:', error); alert('删除规则失败:' + error.message); } } // 获取当前转发规则 async function fetchForwardingRules() { try { const response = await fetch(`/get_rules`); if (!response.ok) { throw new Error('获取规则失败:' + response.statusText); } const rules = await response.json(); const tbody = document.querySelector('#forwardingTable tbody'); tbody.innerHTML = ''; // 清空现有规则 rules.forEach((rule, index) => { const row = document.createElement('tr'); let localPort = '', remoteIP = '', remotePort = ''; // 处理本地端口 if (rule.listen) { [, localPort] = rule.listen.split(':'); } // 处理远程地址和端口 if (rule.remote) { if (rule.remote.includes('[')) { const matches = rule.remote.match(/\[(.*)\]:(.*)$/); if (matches) { remoteIP = matches[1]; remotePort = matches[2]; } } else if (rule.remote.includes(':')) { const colonCount = (rule.remote.match(/:/g) || []).length; if (colonCount > 1) { const lastColon = rule.remote.lastIndexOf(':'); remoteIP = rule.remote.substring(0, lastColon); remotePort = rule.remote.substring(lastColon + 1); } else { [remoteIP, remotePort] = rule.remote.split(':'); } } } row.innerHTML = ` <td>${index + 1}</td> <td>${localPort || 'N/A'}</td> <td>${remoteIP || 'N/A'}</td> <td>${remotePort || 'N/A'}</td> <td><button onclick="deleteRule('${rule.listen || ''}')">删除</button></td> `; tbody.appendChild(row); }); } catch (error) { console.error('获取转发规则失败:', error); alert('获取转发规则失败:' + error.message); } } // 页面加载时获取转发规则和服务状态 window.onload = async function() { await fetchForwardingRules(); await updateServiceStatus(); // 每10秒更新一次状态 setInterval(updateServiceStatus, 10000); }; // 登出功能 document.getElementById('logoutButton').onclick = async function() { try { const response = await fetch('/logout', { method: 'POST' }); if (!response.ok) { throw new Error('登出失败:' + response.statusText); } window.location.href = '/login'; } catch (error) { console.error('登出失败:', error); alert('登出失败:' + error.message); } }; </script> </body> </html>
另外贴一份未经严格测试的,重复端口检测,既是如果本地端口已经在规则中,提示端口被占用 <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Realm 转发管理面板</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f4f4f4; } h1 { color: #333; text-align: center; } .container { max-width: 800px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } button { padding: 10px 15px; margin: 5px; border: none; border-radius: 5px; background-color: #28a745; color: white; cursor: pointer; transition: background-color 0.3s; } button:hover { background-color: #218838; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { border: 1px solid #ddd; padding: 10px; text-align: left; } th { background-color: #f2f2f2; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; } .form-group input { width: calc(100% - 22px); padding: 10px; border: 1px solid #ccc; border-radius: 5px; } #output { margin-top: 20px; background-color: #fff; padding: 10px; border: 1px solid #ccc; border-radius: 5px; min-height: 50px; white-space: pre-wrap; /* 保持空格和换行 */ } .status-tag { display: inline-flex; align-items: center; padding: 4px 8px; font-size: 14px; line-height: 20px; border-radius: 4px; margin-left: 10px; font-weight: 500; } .status-tag.running { background-color: #52c41a; color: white; } .status-tag.stopped { background-color: #ff4d4f; color: white; } .status-wrapper { display: inline-flex; align-items: center; margin-left: 15px; } .status-label { color: #666; margin-right: 8px; } .button-group { display: flex; align-items: center; margin-bottom: 20px; } </style> </head> <body> <div class="container"> <h1>Realm 转发管理面板</h1> <div class="button-group"> <button id="startButton">启动服务</button> <button id="stopButton">停止服务</button> <button id="logoutButton">登出</button> <div class="status-wrapper"> <span class="status-label">状态:</span> <span id="serviceStatus" class="status-tag">检查中...</span> </div> </div> <div id="output"></div> <h2>当前转发规则</h2> <table id="forwardingTable"> <thead> <tr> <th>序号</th> <th>中转端口</th> <th>落地鸡 IP</th> <th>目标端口</th> <th>操作</th> </tr> </thead> <tbody> <!-- 动态填充转发规则 --> </tbody> </table> <h2>添加转发规则</h2> <div class="form-group"> <label for="localPort">中转端口:</label> <input type="number" id="localPort" required> </div> <div class="form-group"> <label for="remoteIP">落地鸡 IP:</label> <input type="text" id="remoteIP" required> </div> <div class="form-group"> <label for="remotePort">目标端口:</label> <input type="number" id="remotePort" required> </div> <button id="addRuleButton">添加规则</button> <h2>批量添加转发规则</h2> <div class="form-group"> <label for="rulesInput">请输入转发规则(每行一个规则,中转端口:落地鸡 IP:目标端口):</label> <textarea id="rulesInput" rows="5" style="width: 100%;"></textarea> </div> <button id="addBatchRulesButton">批量添加规则</button> </div> <script> // 获取当前转发规则 async function fetchForwardingRules() { try { const response = await fetch(`/get_rules`); if (!response.ok) { throw new Error('获取规则失败:' + response.statusText); } const rules = await response.json(); const tableBody = document.querySelector('#forwardingTable tbody'); tableBody.innerHTML = ''; // 清空现有规则 // 创建当前规则的端口占用列表 const usedPorts = new Set(); rules.forEach(rule => { const localPort = rule.listen.split(':')[1]; usedPorts.add(localPort); // 将已占用的端口加入集合 }); // 在表格中显示当前规则 rules.forEach((rule, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${index + 1}</td> <td>${rule.listen.split(':')[1]}</td> <td>${rule.remote.split(':')[0]}</td> <td>${rule.remote.split(':')[1]}</td> <td><button onclick="deleteRule('${rule.listen}')">删除</button></td> `; tableBody.appendChild(row); }); // 返回已占用的端口集合 return usedPorts; } catch (error) { console.error('获取转发规则失败:', error); alert('获取转发规则失败:' + error.message); return new Set(); // 如果获取规则失败,返回空集合 } } // 删除转发规则 async function deleteRule(listenAddress) { try { const response = await fetch(`/delete_rule?listen=${encodeURIComponent(listenAddress)}`, { method: 'DELETE', }); if (response.ok) { alert('规则已删除'); await fetchForwardingRules(); // 刷新规则列表 } else { alert('删除规则失败'); } } catch (error) { console.error('删除规则失败:', error); alert('删除规则失败:' + error.message); } } // 启动服务 document.getElementById('startButton').onclick = async function() { try { const response = await fetch(`/start_service`, { method: 'POST' }); if (!response.ok) { throw new Error('启动服务失败:' + response.statusText); } const data = await response.json(); document.getElementById('output').innerText = data.message || '服务启动成功'; await updateServiceStatus(); // 更新状态 } catch (error) { console.error('启动服务失败:', error); document.getElementById('output').innerText = error.message; } }; // 停止服务 document.getElementById('stopButton').onclick = async function() { try { const response = await fetch(`/stop_service`, { method: 'POST' }); if (!response.ok) { throw new Error('停止服务失败:' + response.statusText); } const data = await response.json(); document.getElementById('output').innerText = data.message || '服务停止成功'; await updateServiceStatus(); // 更新状态 } catch (error) { console.error('停止服务失败:', error); document.getElementById('output').innerText = error.message; } }; // 更新服务状态 async function updateServiceStatus() { try { const response = await fetch(`/check_status`, { method: 'GET' }); if (!response.ok) { throw new Error('检查状态失败:' + response.statusText); } const data = await response.json(); const statusElement = document.getElementById('serviceStatus'); if (data.status === "启用") { statusElement.textContent = "运行中"; statusElement.className = 'status-tag running'; } else { statusElement.textContent = "已停止"; statusElement.className = 'status-tag stopped'; } } catch (error) { console.error('更新状态失败:', error); document.getElementById('serviceStatus').textContent = '未知'; document.getElementById('serviceStatus').className = 'status-tag stopped'; } } // 单个添加转发规则 document.getElementById('addRuleButton').onclick = async function() { const localPort = document.getElementById('localPort').value; const remoteIP = document.getElementById('remoteIP').value; const remotePort = document.getElementById('remotePort').value; try { const usedPorts = await fetchForwardingRules(); // 获取已占用的端口 if (usedPorts.has(localPort)) { alert(`端口 ${localPort} 已被占用,请选择另一个端口`); return; } const response = await fetch(`/add_rule`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ listen: `0.0.0.0:${localPort}`, remote: `${remoteIP}:${remotePort}` }), }); if (!response.ok) { throw new Error('添加规则失败:' + response.statusText); } alert('转发规则添加成功!'); await fetchForwardingRules(); // 刷新规则列表 } catch (error) { console.error('添加规则失败:', error); alert('添加规则失败:' + error.message); } }; // 批量添加转发规则 document.getElementById('addBatchRulesButton').onclick = async function() { const rulesInput = document.getElementById('rulesInput').value.trim(); const rulesList = rulesInput.split('\n').map(line => line.trim()).filter(Boolean); const usedPorts = await fetchForwardingRules(); // 获取已占用的端口 let failedRules = []; for (const rule of rulesList) { const [localPort, remoteIP, remotePort] = rule.split(':'); if (usedPorts.has(localPort)) { failedRules.push(`端口 ${localPort} 已被占用`); continue; } try { const response = await fetch(`/add_rule`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ listen: `0.0.0.0:${localPort}`, remote: `${remoteIP}:${remotePort}` }) }); if (!response.ok) { throw new Error('添加规则失败:' + response.statusText); } usedPorts.add(localPort); // 添加成功后将该端口加入已占用列表 } catch (error) { failedRules.push(`规则 ${rule} 添加失败:${error.message}`); } } if (failedRules.length > 0) { alert(failedRules.join('\n')); } else { alert('所有规则已成功添加'); await fetchForwardingRules(); // 刷新规则列表 } }; // 登出功能 document.getElementById('logoutButton').onclick = async function() { try { const response = await fetch('/logout', { method: 'POST' }); if (!response.ok) { throw new Error('登出失败:' + response.statusText); } window.location.href = '/login'; // 重定向到登录页面 } catch (error) { console.error('登出失败:', error); alert('登出失败:' + error.message); } }; // 页面加载时获取转发规则和服务状态 window.onload = async function() { await fetchForwardingRules(); await updateServiceStatus(); // 每10秒更新一次服务状态 setInterval(updateServiceStatus, 10000); }; </script> </body> </html>
@wcwq99 #77 嗯,大佬,只能在脚本停止面板,然后,启动面板,面板里面操作Realm 的重启不行的
bd
@道法自然 #74
大佬,在您的单个添加基础上,实现了批量添加,请您核实,如果可能,麻烦您更新github,
realm/web/templates/index.html
bd
另外贴一份未经严格测试的,重复端口检测,既是如果本地端口已经在规则中,提示端口被占用
好贴帮顶,收藏
@小兔子 #85
兔子佬 66的
@niudapao #72 嗯?有点没理解 是放行服务器端口吗
@小兔子 #85 好,感谢老哥,我周末回去测试没问题再更新
膜拜,大佬装完这个可以在面板上自己配置转发吗?