diff --git a/README.md b/README.md index 96e62bd..d65cd6f 100644 --- a/README.md +++ b/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) -📢 Docker Proxy-TG交流群 +📢 Docker Proxy-交流群 diff --git a/hubcmdui/package.json b/hubcmdui/package.json index 729b065..778cd79 100644 --- a/hubcmdui/package.json +++ b/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" } } diff --git a/hubcmdui/server.js b/hubcmdui/server.js index 65005af..117a862 100644 --- a/hubcmdui/server.js +++ b/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, () => { diff --git a/hubcmdui/web/admin.html b/hubcmdui/web/admin.html index fbbf5c7..2c8e936 100644 --- a/hubcmdui/web/admin.html +++ b/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; + } @@ -486,6 +506,7 @@
  • 广告管理
  • 文档管理
  • 修改密码
  • +
  • 网络测试
  • Docker 服务状态
  • @@ -574,6 +595,35 @@ + +
    +

    网络测试

    +
    + + + +
    +
    + + +
    + +
    +
    +

    Docker 服务状态

    @@ -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) {