diff --git a/README.md b/README.md
index 96e62bd..d65cd6f 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
[](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) {