瀏覽代碼

feat: Add network testing functionality.

dqzboy 1 年之前
父節點
當前提交
f51655df38
共有 4 個文件被更改,包括 173 次插入1 次删除
  1. 1 1
      README.md
  2. 1 0
      hubcmdui/package.json
  3. 34 0
      hubcmdui/server.js
  4. 137 0
      hubcmdui/web/admin.html

+ 1 - 1
README.md

@@ -20,7 +20,7 @@
 [![GitHub license](https://img.shields.io/github/license/dqzboy/Docker-Proxy)](https://github.com/dqzboy/Docker-Proxy/blob/main/LICENSE)
 
 
-📢 <a href="https://t.me/+ghs_XDp1vwxkMGU9" style="font-size: 15px;">Docker Proxy-TG交流群</a>
+📢 <a href="https://t.me/+ghs_XDp1vwxkMGU9" style="font-size: 15px;">Docker Proxy-交流群</a> 
 
 </div>
 

+ 1 - 0
hubcmdui/package.json

@@ -7,6 +7,7 @@
     "express": "^4.19.2",
     "express-session": "^1.18.0",
     "morgan": "^1.10.0",
+    "validator": "^13.12.0",
     "ws": "^8.18.0"
   }
 }

+ 34 - 0
hubcmdui/server.js

@@ -12,6 +12,8 @@ const app = express();
 const cors = require('cors');
 const WebSocket = require('ws');
 const http = require('http');
+const { exec } = require('child_process'); // 网络测试
+const validator = require('validator');
 
 let docker = null;
 
@@ -646,6 +648,38 @@ app.post('/api/docker/delete/:id', requireLogin, async (req, res) => {
   }
 });
 
+
+// 网络测试
+const { execSync } = require('child_process');
+
+// 在应用启动时执行
+const pingPath = execSync('which ping').toString().trim();
+const traceroutePath = execSync('which traceroute').toString().trim();
+
+app.post('/api/network-test', requireLogin, (req, res) => {
+  const { domain, testType } = req.body;
+
+  let command;
+  switch (testType) {
+      case 'ping':
+          command = `${pingPath} -c 4 ${domain}`;
+          break;
+      case 'traceroute':
+          command = `${traceroutePath}  -m 10 ${domain}`;
+          break;
+      default:
+          return res.status(400).send('无效的测试类型');
+  }
+
+  exec(command, { timeout: 30000 }, (error, stdout, stderr) => {
+      if (error) {
+          console.error(`执行出错: ${error}`);
+          return res.status(500).send('测试执行失败');
+      }
+      res.send(stdout || stderr);
+  });
+});
+
 // 启动服务器
 const PORT = process.env.PORT || 3000;
 server.listen(PORT, () => {

+ 137 - 0
hubcmdui/web/admin.html

@@ -470,6 +470,26 @@
             0% { transform: rotate(0deg); }
             100% { transform: rotate(360deg); }
         }
+
+        #network-test .input-group {
+            margin-bottom: 15px;
+            max-width: 400px;
+        }
+        #network-test label {
+            display: block;
+            margin-bottom: 5px;
+        }
+        #network-test input[type="text"],
+        #network-test select {
+            width: 100%;
+            padding: 8px;
+            border: 1px solid #d1d5da;
+            border-radius: 4px;
+            box-sizing: border-box;
+        }
+        #network-test button {
+            margin-top: 10px;
+        }
     </style>
 </head>
 <body>
@@ -486,6 +506,7 @@
                 <li data-section="ad-management">广告管理</li>
                 <li data-section="documentation-management">文档管理</li>            
                 <li data-section="password-change">修改密码</li>
+                <li data-section="network-test">网络测试</li>
                 <li data-section="docker-status">Docker 服务状态</li>
             </ul>
         </div>
@@ -574,6 +595,35 @@
                     <button type="button" onclick="changePassword()">修改密码</button>
                 </div>
 
+                <!-- 网络测试 -->
+                <div id="network-test" class="content-section">
+                    <h1 class="admin-title">网络测试</h1>
+                    <div class="input-group">
+                        <label for="testDomain">目标域名或IP地址:</label>
+                        <input type="text" id="testDomain" placeholder="例如: www.example.com 或 8.8.8.8">
+                        <select id="domainSelect">
+                            <option value="">选择预定义域名</option>
+                            <option value="gcr.io">gcr.io</option>
+                            <option value="ghcr.io">ghcr.io</option>
+                            <option value="quay.io">quay.io</option>
+                            <option value="k8s.gcr.io">k8s.gcr.io</option>
+                            <option value="registry.k8s.io">registry.k8s.io</option>
+                            <option value="mcr.microsoft.com">mcr.microsoft.com</option>
+                            <option value="docker.elastic.com">docker.elastic.com</option>
+                            <option value="registry-1.docker.io">registry-1.docker.io</option>
+                        </select>
+                    </div>
+                    <div class="input-group">
+                        <label for="testType">测试类型:</label>
+                        <select id="testType">
+                            <option value="ping">Ping</option>
+                            <option value="traceroute">Traceroute</option>
+                        </select>
+                    </div>
+                    <button>开始测试</button>
+                    <div id="testResults" style="margin-top: 20px; white-space: pre-wrap; font-family: monospace;"></div>
+                </div>
+
                 <!-- Docker服务状态 -->
                 <div id="docker-status" class="content-section">
                     <h1 class="admin-title">Docker 服务状态</h1>
@@ -1708,10 +1758,97 @@
             }
         }
 
+
         document.addEventListener('DOMContentLoaded', function() {
             const sidebarItems = document.querySelectorAll('.sidebar li');
             const contentSections = document.querySelectorAll('.content-section');
 
+            const testDomainInput = document.getElementById('testDomain');
+
+            const domainSelect = document.getElementById('domainSelect');
+
+            // 当选择预定义域名时,更新输入框
+            domainSelect.addEventListener('change', function() {
+                if (this.value) {
+                    testDomainInput.value = this.value;
+                }
+            });
+
+            // 当输入框获得焦点时,清空选择框
+            testDomainInput.addEventListener('focus', function() {
+                domainSelect.value = '';
+            });
+
+            // 网络测试函数
+            function runNetworkTest() {
+                const domain = testDomainInput.value.trim();
+                const testType = document.getElementById('testType').value;
+                const resultsDiv = document.getElementById('testResults');
+
+                // 验证输入
+                if (!domain) {
+                    alert('请输入目标域名或IP地址');
+                    return;
+                }
+
+                // 验证域名格式
+                const domainRegex = /^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i;
+                // 验证IPv4格式
+                const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+                // 验证IPv6格式
+                const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
+
+                // 检查是否为预定义的选项之一
+                const predefinedOptions = [
+                    'gcr.io', 'ghcr.io', 'quay.io', 'k8s.gcr.io', 'registry.k8s.io',
+                    'mcr.microsoft.com', 'docker.elastic.com', 'registry-1.docker.io'
+                ];
+
+                if (!predefinedOptions.includes(domain) && 
+                    !domainRegex.test(domain) && 
+                    !ipv4Regex.test(domain) && 
+                    !ipv6Regex.test(domain)) {
+                    alert('请输入有效的域名或IP地址');
+                    return;
+                }
+
+                resultsDiv.innerHTML = '测试中,请稍候...';
+
+                const controller = new AbortController();
+                const timeoutId = setTimeout(() => controller.abort(), 60000); // 60秒超时
+
+                fetch('/api/network-test', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json',
+                    },
+                    body: JSON.stringify({ domain, testType }),
+                    signal: controller.signal
+                })
+                .then(response => {
+                    clearTimeout(timeoutId);
+                    if (!response.ok) {
+                        throw new Error('网络测试失败');
+                    }
+                    return response.text();
+                })
+                .then(result => {
+                    resultsDiv.textContent = result;
+                })
+                .catch(error => {
+                    console.error('网络测试出错:', error);
+                    if (error.name === 'AbortError') {
+                        resultsDiv.textContent = '测试超时,请稍后再试';
+                    } else {
+                        resultsDiv.textContent = '测试失败: ' + error.message;
+                    }
+                });
+            }
+
+            // 绑定测试按钮点击事件
+            document.querySelector('#network-test button').addEventListener('click', runNetworkTest);
+
+
             let isDockerStatusLoaded = false;
 
             function showSection(sectionId) {