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