diff --git a/.gitignore b/.gitignore index 5443028..cf8d3dd 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +node_modules \ No newline at end of file diff --git a/hubcmdui/documentation/1743543400369.json b/hubcmdui/documentation/1743543400369.json index b049198..8aa066f 100644 --- a/hubcmdui/documentation/1743543400369.json +++ b/hubcmdui/documentation/1743543400369.json @@ -3,5 +3,5 @@ "content": "# Podman 配置镜像加速\n\n* 修改配置文件 `/etc/containers/registries.conf`,添加配置:\n\n```bash\nunqualified-search-registries = ['docker.io', 'k8s.gcr.io', 'gcr.io', 'ghcr.io', 'quay.io']\n\n[[registry]]\nprefix = \"docker.io\"\ninsecure = true\nlocation = \"registry-1.docker.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"k8s.gcr.io\"\ninsecure = true\nlocation = \"k8s.gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"gcr.io\"\ninsecure = true\nlocation = \"gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"ghcr.io\"\ninsecure = true\nlocation = \"ghcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"quay.io\"\ninsecure = true\nlocation = \"quay.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n```# Podman 配置镜像加速\n\n* 修改配置文件 `/etc/containers/registries.conf`,添加配置:\n\n```bash\nunqualified-search-registries = ['docker.io', 'k8s.gcr.io', 'gcr.io', 'ghcr.io', 'quay.io']\n\n[[registry]]\nprefix = \"docker.io\"\ninsecure = true\nlocation = \"registry-1.docker.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"k8s.gcr.io\"\ninsecure = true\nlocation = \"k8s.gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"gcr.io\"\ninsecure = true\nlocation = \"gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"ghcr.io\"\ninsecure = true\nlocation = \"ghcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"quay.io\"\ninsecure = true\nlocation = \"quay.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n```", "published": true, "createdAt": "2025-04-01T21:36:40.369Z", - "updatedAt": "2025-04-01T21:36:41.977Z" + "updatedAt": "2025-05-08T15:16:47.900Z" } \ No newline at end of file diff --git a/hubcmdui/package.json b/hubcmdui/package.json index c604261..1d17120 100644 --- a/hubcmdui/package.json +++ b/hubcmdui/package.json @@ -30,6 +30,7 @@ "node-cache": "^5.1.2", "p-limit": "^4.0.0", "session-file-store": "^1.5.0", + "systeminformation": "^5.25.11", "validator": "^13.7.0", "ws": "^8.8.1" }, diff --git a/hubcmdui/services/systemService.js b/hubcmdui/services/systemService.js index 1fd65d3..53a8212 100644 --- a/hubcmdui/services/systemService.js +++ b/hubcmdui/services/systemService.js @@ -1,55 +1,118 @@ /** * 系统服务模块 - 处理系统级信息获取 + * 使用 systeminformation 库来提供跨平台的系统数据 */ -const { exec } = require('child_process'); -const os = require('os'); +const si = require('systeminformation'); const logger = require('../logger'); +const os = require('os'); // os模块仍可用于某些特定情况或日志记录 -// 获取磁盘空间信息 -async function getDiskSpace() { - try { - // 根据操作系统不同有不同的命令 - const isWindows = os.platform() === 'win32'; - - if (isWindows) { - // Windows实现(需要更复杂的逻辑) - return { - diskSpace: '未实现', - usagePercent: 0 - }; - } else { - // Linux/Mac实现 - const diskInfo = await execPromise('df -h | grep -E "/$|/home" | head -1'); - const diskParts = diskInfo.split(/\s+/); - - if (diskParts.length >= 5) { - return { - diskSpace: `${diskParts[2]}/${diskParts[1]}`, - usagePercent: parseInt(diskParts[4].replace('%', '')) - }; - } else { - throw new Error('磁盘信息格式不正确'); - } +// Helper function to format bytes into a more readable format +function formatBytes(bytes, decimals = 2) { + if (bytes === null || bytes === undefined || isNaN(bytes) || bytes === 0) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + try { + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } catch (e) { + return 'N/A'; // In case of Math.log error with very small numbers etc. } - } catch (error) { - logger.error('获取磁盘空间失败:', error); - throw error; - } } -// 辅助函数: 执行命令 -function execPromise(command) { - return new Promise((resolve, reject) => { - exec(command, (error, stdout, stderr) => { - if (error) { - reject(error); - return; - } - resolve(stdout.trim()); - }); - }); +// 获取核心系统资源信息 (CPU, Memory, Disk) +async function getSystemResources() { + try { + logger.info('Fetching system resources using systeminformation...'); + + // 并行获取数据以提高效率 + const [cpuInfo, memInfo, fsInfo, cpuLoadInfo, osInfo] = await Promise.all([ + si.cpu(), // For CPU model, cores, speed + si.mem(), // For memory details + si.fsSize(), // For filesystem details + si.currentLoad(), // For current CPU load percentage and per-core load + si.osInfo() // For OS type, mainly for specific disk selection if needed + ]); + + // --- CPU 信息处理 --- + let cpuUsage = parseFloat(cpuLoadInfo.currentLoad.toFixed(1)); + // Fallback if currentLoad is not a number (very unlikely with systeminformation) + if (isNaN(cpuUsage) && Array.isArray(cpuLoadInfo.cpus) && cpuLoadInfo.cpus.length > 0) { + // Calculate average from per-core loads if overall isn't good + const totalLoad = cpuLoadInfo.cpus.reduce((acc, core) => acc + core.load, 0); + cpuUsage = parseFloat((totalLoad / cpuLoadInfo.cpus.length).toFixed(1)); + } + if (isNaN(cpuUsage)) cpuUsage = null; // Final fallback to null if still NaN + + const cpuData = { + cores: cpuInfo.cores, + physicalCores: cpuInfo.physicalCores, + model: cpuInfo.manufacturer + ' ' + cpuInfo.brand, + speed: cpuInfo.speed, // in GHz + usage: cpuUsage, // Overall CPU usage percentage + loadAvg: osInfo.platform !== 'win32' ? os.loadavg().map(load => parseFloat(load.toFixed(1))) : null // os.loadavg() is not for Windows + }; + + // --- 内存信息处理 --- + // systeminformation already provides these in bytes + const memData = { + total: memInfo.total, + free: memInfo.free, // Truly free + used: memInfo.used, // total - free (includes buff/cache on Linux, PhysMem used on macOS) + active: memInfo.active, // More representative of app-used memory + available: memInfo.available, // Memory available to applications (often free + reclaimable buff/cache) + wired: memInfo.wired, // macOS specific: memory that cannot be paged out + // compressed: memInfo.compressed, // macOS specific, if systeminformation lib provides it directly + buffcache: memInfo.buffcache // Linux specific: buffer and cache + }; + + // --- 磁盘信息处理 --- + // Find the primary disk (e.g., mounted on '/' for Linux/macOS, or C: for Windows) + let mainDiskInfo = null; + if (osInfo.platform === 'win32') { + mainDiskInfo = fsInfo.find(d => d.fs.startsWith('C:')); + } else { + mainDiskInfo = fsInfo.find(d => d.mount === '/'); + } + if (!mainDiskInfo && fsInfo.length > 0) { + // Fallback to the first disk if the standard one isn't found + mainDiskInfo = fsInfo[0]; + } + + const diskData = mainDiskInfo ? { + mount: mainDiskInfo.mount, + size: formatBytes(mainDiskInfo.size), + used: formatBytes(mainDiskInfo.used), + available: formatBytes(mainDiskInfo.available), // systeminformation provides 'available' + percent: mainDiskInfo.use !== null && mainDiskInfo.use !== undefined ? mainDiskInfo.use.toFixed(0) + '%' : 'N/A' + } : { + mount: 'N/A', + size: 'N/A', + used: 'N/A', + available: 'N/A', + percent: 'N/A' + }; + + const resources = { + osType: osInfo.platform, // e.g., 'darwin', 'linux', 'win32' + osDistro: osInfo.distro, + cpu: cpuData, + memory: memData, + disk: diskData + }; + logger.info('Successfully fetched system resources:', /* JSON.stringify(resources, null, 2) */ resources.osType); + return resources; + + } catch (error) { + logger.error('获取系统资源失败 (services/systemService.js):', error); + // Return a structured error object or rethrow, + // so the API route can send an appropriate HTTP error + throw new Error(`Failed to get system resources: ${error.message}`); + } } module.exports = { - getDiskSpace + getSystemResources + // getDiskSpace, if it was previously exported and used by another route, can be kept + // or removed if getSystemResources now covers all disk info needs for /api/system-resources }; diff --git a/hubcmdui/users.json b/hubcmdui/users.json index f082238..2dbc255 100644 --- a/hubcmdui/users.json +++ b/hubcmdui/users.json @@ -3,8 +3,8 @@ { "username": "root", "password": "$2b$10$lh1kqJtq3shL2BhMD1LbVOThGeAlPXsDgME/he4ZyDMRupVtj0Hl.", - "loginCount": 1, - "lastLogin": "2025-04-01T22:45:10.184Z" + "loginCount": 0, + "lastLogin": "2025-05-08T14:59:22.166Z" } ] } \ No newline at end of file diff --git a/hubcmdui/web/admin.html b/hubcmdui/web/admin.html index c4b8ed6..370a24e 100644 --- a/hubcmdui/web/admin.html +++ b/hubcmdui/web/admin.html @@ -1518,6 +1518,272 @@ color: #333; margin: 0; } + + /* 菜单管理 - 新增行样式 */ + #new-menu-item-row input[type="text"], + #new-menu-item-row select { + /* 使用 Bootstrap 的 form-control-sm 已经减小了 padding 和 font-size */ + margin-bottom: 0; /* 移除下方外边距,使其在表格行内更紧凑 */ + } + + #new-menu-item-row .form-control-sm { + padding: 0.3rem 0.6rem; /* 微调内边距 */ + font-size: 0.875rem; /* 统一字体大小 */ + } + + #new-menu-item-row .form-select-sm { + padding: 0.3rem 1.5rem 0.3rem 0.6rem; /* 微调 select 内边距以适应箭头 */ + font-size: 0.875rem; + } + + .action-buttons-new-menu .btn { + margin-right: 5px; /* 按钮间距 */ + min-width: 80px; /* 给按钮一个最小宽度 */ + } + + .action-buttons-new-menu .btn:last-child { + margin-right: 0; + } + + .action-buttons-new-menu .btn i { + margin-right: 4px; /* 图标和文字间距 */ + } + + /* 使新行单元格垂直居中 */ + #new-menu-item-row td { + vertical-align: middle; + } + + /* 网络测试页面美化 */ + #network-test { + /* 可以考虑将整个 #network-test 作为一个卡片,如果它还没有被 .content-section 样式化为卡片的话 */ + } + + /* 直接覆盖#testResults.loading的样式,防止旋转 */ + #network-test #testResults.loading { + animation: none !important; + border: none !important; + display: block !important; + position: relative !important; + text-align: center !important; + padding: 15px !important; + color: var(--text-secondary, #6c757d) !important; + font-size: 1rem !important; + width: auto !important; + height: auto !important; + } + + /* 使用:before添加文本内容 */ + #network-test #testResults.loading:before { + content: "测试进行中..." !important; + animation: none !important; + border: none !important; + position: absolute !important; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; + color: var(--text-light, #e9ecef) !important; + font-size: 1rem !important; + font-weight: 500 !important; + padding: 0 !important; + background-color: transparent !important; + z-index: 10 !important; + text-align: center !important; + } + + #network-test .form-row { + display: flex; + flex-wrap: wrap; /* 允许换行到小屏幕 */ + gap: 1.5rem; /* 各个元素之间的间距 */ + margin-bottom: 1.5rem; + align-items: flex-end; /* 使得按钮和选择框底部对齐 */ + } + + #network-test .form-group { + flex: 1; /* 让表单组占据可用空间 */ + min-width: 250px; /* 避免在小屏幕上过于挤压 */ + background-color: transparent; /* 移除之前 #network-test .input-group 的背景色 */ + padding: 0; /* 移除内边距 */ + box-shadow: none; /* 移除阴影 */ + border-radius: 0; + } + + #network-test label { + display: block; + margin-bottom: 0.5rem; + color: var(--text-primary); + font-weight: 500; + font-size: 0.95rem; + } + + #network-test .form-control, + #network-test .form-select { + width: 100%; + padding: 0.75rem 1rem; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background-color: var(--input-bg, var(--container-bg)); /* 允许自定义输入背景或使用容器背景 */ + color: var(--text-primary); + font-size: 0.95rem; + transition: all var(--transition-fast); + box-shadow: var(--shadow-sm); + } + + #network-test .form-control:focus, + #network-test .form-select:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(var(--primary-rgb, 61, 124, 244), 0.25); /* 使用RGB变量 */ + outline: none; + } + + #network-test .test-controls-container { + /* 这个容器包裹选择器和按钮 */ + display: flex; + flex-wrap: wrap; /* 允许换行 */ + gap: 1rem; /* 控件之间的间距 */ + align-items: flex-end; /* 使得按钮和选择框底部对齐 */ + margin-bottom: 1.5rem; + } + + #network-test .test-controls-container .form-group { + flex-grow: 1; + flex-basis: 200px; /* 给每个控件一个基础宽度 */ + } + + #network-test .start-test-btn { + padding: 0.75rem 1.5rem; + font-size: 1rem; + font-weight: 500; + white-space: nowrap; /* 防止按钮文字换行 */ + height: fit-content; /* 与调整后的 select 高度匹配 */ + flex-shrink: 0; /* 防止按钮在 flex 布局中被压缩 */ + } + + #network-test .start-test-btn i { + margin-right: 0.5rem; + } + + .results-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; + padding: 0.5rem 0; + border-bottom: 1px solid var(--border-light); + } + + .results-header h3 { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-light, #e9ecef); /* Changed to light color for dark background */ + margin: 0; + } + + #clearTestResultsBtn { + font-size: 0.85rem; + padding: 0.3rem 0.8rem; + } + + #testResultsContainer { + background-color: var(--container-bg-dark, #1e2532); /* 使用变量或默认暗色 */ + border-radius: var(--radius-md); + box-shadow: var(--shadow-md); + margin-top: 1rem; + padding: 0; /* 移除外层padding,让头部和内容区自己控制 */ + position: relative; /* Add this to be a positioning context for absolute children if needed by headers etc. */ + } + + #testResults { + padding: 1rem; + color: var(--text-light, #e9ecef); + font-family: var(--font-mono, 'JetBrains Mono', monospace); + font-size: 0.9rem; + white-space: pre-wrap; + word-break: break-all; + min-height: 200px; + max-height: 400px; + overflow-y: auto; + background-color: transparent; + border-radius: 0 0 var(--radius-md) var(--radius-md); + box-shadow: none; + border: none; + margin-top: 0; + position: relative; + } + + /* Use higher specificity and !important to force override */ + #network-test #testResults.loading::before { + content: "测试进行中..."; /* Force text content */ + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); /* Center the text block */ + color: var(--text-secondary, #6c757d); + font-size: 1rem; + font-weight: 500; + padding: 0.75rem 1.25rem; + background-color: transparent !important; + border: none !important; /* 移除边框 */ + border-radius: var(--radius-md, 0.375rem); + z-index: 10; + text-align: center; + /* Force removal of spinner styles */ + animation: none !important; + border-top-color: transparent !important; /* Force override any spinner head */ + width: auto !important; /* Force width */ + height: auto !important; /* Force height */ + box-sizing: border-box; /* Ensure padding is included correctly */ + } + + /* Ensure keyframes for the spinner are removed or commented out */ + /* @keyframes testResultSpinner { to { transform: rotate(360deg); } } */ + + /* Style for the
tag inside #testResults for consistent output formatting */
+ #testResults pre {
+ margin: 0; /* Remove default pre margin */
+ font-family: inherit; /* Inherit from #testResults (monospace) */
+ font-size: inherit; /* Inherit from #testResults */
+ color: inherit; /* Inherit from #testResults */
+ white-space: pre-wrap; /* Ensure wrapping */
+ word-break: break-all; /* Ensure long lines break */
+ background-color: transparent; /* Ensure no pre background interferes */
+ padding: 0; /* No extra padding for pre, parent div handles it */
+ }
+
+ #testResults pre.text-danger {
+ color: var(--danger-color, #dc3545); /* Ensure error text is colored */
+ }
+
+ /* Placeholder text styling (if using ) */
+ #testResults .text-muted.text-center {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: calc(100% - 2rem); /* Consider padding of #testResults */
+ }
+
+ /* 保持容器监控页面已停止容器列表的表头在鼠标悬停时背景色不变 */
+ #docker-monitoring .container-table th:hover {
+ background-color: var(--primary-color);
+ color: white;
+ }
+
+ /* SweetAlert2 弹窗内容文本居中 */
+ #swal2-html-container, /* 使用 ID 提高特异性 */
+ .swal2-html-container { /* 保留类选择器作为备用 */
+ text-align: center !important;
+ }
+
+ #swal2-html-container p,
+ .swal2-html-container p {
+ text-align: center !important;
+ }
+
+ #swal2-html-container div,
+ .swal2-html-container div {
+ text-align: center !important;
+ }
@@ -1636,11 +1902,6 @@
文档管理
-
-
-
@@ -1650,6 +1911,12 @@
+
+
+
+
@@ -1678,20 +1945,40 @@
网络测试
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+ 测试结果
+
+
+
+
+
-
-
diff --git a/hubcmdui/web/index.html b/hubcmdui/web/index.html
index e468e09..811f3b2 100644
--- a/hubcmdui/web/index.html
+++ b/hubcmdui/web/index.html
@@ -200,6 +200,7 @@
footer.innerHTML = `Copyright © ${new Date().getFullYear()} Docker-Proxy All Rights Reserved. GitHub
`;
}
}
+ window.protectCopyright = protectCopyright;
// ========================================
// === 文档加载相关函数 (移到此处) ===
@@ -208,7 +209,7 @@
async function loadAndDisplayDocumentation() {
// 防止重复加载
if (documentationLoaded) {
- console.log('文档已加载,跳过重复加载');
+ // console.log('文档已加载,跳过重复加载');
return;
}
@@ -216,12 +217,12 @@
const docContentContainer = document.getElementById('documentationText');
if (!docListContainer || !docContentContainer) {
- console.warn('找不到文档列表或内容容器,可能不是文档页面');
+ // console.warn('找不到文档列表或内容容器,可能不是文档页面');
return; // 如果容器不存在,则不执行加载
}
try {
- console.log('开始加载文档列表和内容...');
+ // console.log('开始加载文档列表和内容...');
// 显示加载状态
docListContainer.innerHTML = ' 正在加载文档列表...';
@@ -234,7 +235,7 @@
}
const data = await response.json();
- console.log('获取到文档列表:', data);
+ // console.log('获取到文档列表:', data);
// 保存到全局变量
window.documentationData = data;
@@ -322,9 +323,30 @@
}
}
}
- // ========================================
- // === 文档加载相关函数结束 ===
- // ========================================
+ //
+ function useImage(imageName) {
+ // 切换到镜像加速标签页
+ switchTab('accelerate');
+
+ // 填充镜像名称到输入框
+ const imageInput = document.getElementById('imageInput');
+ if (imageInput) {
+ imageInput.value = imageName;
+
+ // 自动生成加速命令
+ generateCommands(imageName);
+
+ // 滚动到结果区域
+ const resultDiv = document.getElementById('result');
+ if (resultDiv) {
+ resultDiv.scrollIntoView({ behavior: 'smooth' });
+ }
+ }
+
+ // 显示用户友好的提示
+ showToastNotification(`已选择镜像: ${imageName}`, 'success');
+ }
+ window.useImage = useImage;
// ========================================
// === 全局变量和状态 ===
@@ -346,7 +368,7 @@
const config = await response.json();
if (config.proxyDomain) {
proxyDomain = config.proxyDomain;
- console.log('成功加载代理域名:', proxyDomain);
+ // console.log('成功加载代理域名:', proxyDomain);
} else {
console.warn('配置中没有proxyDomain字段');
proxyDomain = 'registry-1.docker.io'; // 使用默认值
@@ -362,7 +384,7 @@
}
// ========================================
- // === 新增:全局提示函数 ===
+ // === 全局提示函数 ===
// ========================================
function showToastNotification(message, type = 'info') { // types: info, success, error
// 移除任何现有的通知
@@ -438,19 +460,52 @@
loadAndDisplayDocumentation();
} else if (tabName === 'accelerate') {
// 重置显示状态
- document.querySelector('.quick-guide').style.display = 'block';
- document.querySelector('.popular-images').style.display = 'block';
- document.getElementById('result').style.display = 'none';
+ const quickGuideEl = document.querySelector('.quick-guide');
+ if (quickGuideEl) quickGuideEl.style.display = 'block';
+
+ const popularImagesEl = document.querySelector('.popular-images');
+ if (popularImagesEl) popularImagesEl.style.display = 'block';
+
+ const accelerateFeaturesEl = document.querySelector('#accelerateContent .features');
+ if (accelerateFeaturesEl) accelerateFeaturesEl.style.display = 'grid';
+
+ const resultEl = document.getElementById('result');
+ if (resultEl) resultEl.style.display = 'none';
- const imageInput = document.getElementById('imageInput').value.trim();
- if (imageInput) {
- generateCommands(imageInput);
- }
- document.getElementById('searchInput').value = '';
- document.getElementById('searchResults').innerHTML = '';
- document.querySelector('.popular-images').style.display = 'block';
+ // 清空搜索相关的输入和结果,因为我们切换到了加速标签
+ const searchInputEl = document.getElementById('searchInput');
+ if(searchInputEl) searchInputEl.value = '';
+
+ const searchResultsEl = document.getElementById('searchResults');
+ if(searchResultsEl) searchResultsEl.innerHTML = '';
}
}
+ window.switchTab = switchTab;
+
+ // 新增:返回搜索结果视图
+ function showSearchResults() {
+ const searchResultsList = document.getElementById('searchResultsList');
+ const imageTagsView = document.getElementById('imageTagsView');
+ const searchResults = document.getElementById('searchResults');
+ const paginationContainer = document.getElementById('paginationContainer');
+ const features = document.querySelector('#searchContent .features'); // 获取特性区域
+
+ if (searchResultsList) searchResultsList.style.display = 'block';
+ if (imageTagsView) imageTagsView.style.display = 'none';
+
+ // 检查 searchResults 是否有内容并且不是 "未找到" 消息
+ if (searchResults && searchResults.innerHTML.trim() !== '' && !searchResults.querySelector('.empty-result')) {
+ searchResults.style.display = 'block';
+ if (paginationContainer) paginationContainer.style.display = 'flex';
+ if (features) features.style.display = 'none'; // 隐藏特性区
+ } else {
+ // 如果 searchResults 为空, 或者包含 "未找到" 消息
+ if (searchResults) searchResults.style.display = 'block'; // 保持 searchResults 区域可见以显示 "未找到"
+ if (paginationContainer) paginationContainer.style.display = 'none';
+ if (features) features.style.display = 'grid'; // 显示特性区
+ }
+ }
+ window.showSearchResults = showSearchResults;
// 添加formatNumber函数定义
function formatNumber(num) {
@@ -467,15 +522,18 @@
}
// 生成加速命令
- function generateCommands(imageInput) {
- if (!imageInput) {
- imageInput = document.getElementById('imageInput').value.trim();
+ function generateCommands(imageNameInput) {
+ let currentImageName = imageNameInput;
+ if (!currentImageName) {
+ const imageInputEl = document.getElementById('imageInput');
+ if (imageInputEl) currentImageName = imageInputEl.value.trim();
}
- if (!imageInput) {
+
+ if (!currentImageName) {
alert('请输入 Docker 镜像名称');
return;
}
- let [imageName, tag] = imageInput.split(':');
+ let [imageName, tag] = currentImageName.split(':');
tag = tag || 'latest';
let originalImage = `${imageName}:${tag}`;
@@ -518,48 +576,46 @@
});
// 显示结果并隐藏其他内容
- resultDiv.style.display = 'flex';
- resultDiv.style.flexDirection = 'column';
- document.querySelector('.quick-guide').style.display = 'none';
- document.querySelector('.features').style.display = 'none';
+ if (resultDiv) {
+ resultDiv.style.display = 'flex';
+ resultDiv.style.flexDirection = 'column';
+ }
+
+ const quickGuideEl = document.querySelector('.quick-guide');
+ if (quickGuideEl) quickGuideEl.style.display = 'none';
+
+ const accelerateFeaturesEl = document.querySelector('#accelerateContent .features');
+ if (accelerateFeaturesEl) accelerateFeaturesEl.style.display = 'none';
}
+ window.generateCommands = generateCommands;
// 复制命令到剪贴板
function copyToClipboard(text, element) {
+ // console.log('[copyToClipboard] Received text to copy:', text); // Debug log
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
- showNotification();
+ showToastNotification('已复制到剪贴板', 'success');
}, (err) => {
console.error('无法复制文本: ', err);
+ showToastNotification('复制失败: ' + err.message, 'error');
});
} else {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
- document.execCommand('copy');
- document.body.removeChild(textarea);
- showNotification();
+ try {
+ document.execCommand('copy');
+ showToastNotification('已复制到剪贴板', 'success');
+ } catch (err) {
+ console.error('无法使用 execCommand 复制文本: ', err);
+ showToastNotification('复制失败: ' + err.message, 'error');
+ } finally {
+ document.body.removeChild(textarea);
+ }
}
}
-
- function showNotification() {
- // 移除任何现有的通知
- const existingNotification = document.querySelector('.copy-success');
- if (existingNotification) {
- existingNotification.remove();
- }
- // 创建新的通知
- const successDiv = document.createElement('div');
- successDiv.className = 'copy-success';
- successDiv.textContent = '已复制到剪贴板';
- // 添加到body而不是pre元素
- document.body.appendChild(successDiv);
- // 1.5秒后移除提示
- setTimeout(() => {
- successDiv.remove();
- }, 1500);
- }
+ window.copyToClipboard = copyToClipboard;
// 改进的API请求函数,支持自动重试
async function fetchWithRetry(url, options = {}, retries = 3, retryDelay = 1000) {
@@ -619,7 +675,7 @@
document.getElementById('imageTagsView').style.display = 'none';
try {
- console.log(`搜索Docker Hub: 关键词=${searchTerm}, 页码=${page}`);
+ // console.log(`搜索Docker Hub: 关键词=${searchTerm}, 页码=${page}`);
// 使用新的fetchWithRetry函数
const data = await fetchWithRetry(
@@ -662,6 +718,7 @@
document.getElementById('paginationContainer').style.display = 'none';
}
}
+ window.searchDockerHub = searchDockerHub;
// 更新分页控件
function updatePagination(currentPage, totalPages) {
@@ -748,10 +805,10 @@
try {
// 使用新的fetchWithRetry函数获取标签计数
const countApiUrl = `/api/dockerhub/tag-count?name=${encodeURIComponent(currentImageData.name)}&official=${currentImageData.isOfficial}`;
- console.log('Requesting tag count from:', countApiUrl);
+ // console.log('Requesting tag count from:', countApiUrl);
const countData = await fetchWithRetry(countApiUrl);
- console.log('Received tag count data:', countData);
+ // console.log('Received tag count data:', countData);
const tagCount = countData.count || 0;
const recommendedMode = countData.recommended_mode || 'paginated';
@@ -837,6 +894,7 @@
showToastNotification(`加载镜像详情失败: ${error.message}`, 'error');
}
}
+ window.viewImageDetails = viewImageDetails;
// 新增: 加载所有标签 - 改进错误处理
async function loadAllTags() {
@@ -965,6 +1023,7 @@
loadAllTagsBtn.innerHTML = ' 加载全部TAG';
}
}
+ window.loadAllTags = loadAllTags;
// 添加 loadImageTags 函数定义
async function loadImageTags(page = 1) {
@@ -978,11 +1037,11 @@
try {
// 构建API URL
const apiUrl = `/api/dockerhub/tags?name=${encodeURIComponent(currentImageData.name)}&official=${currentImageData.isOfficial}&page=${page}&page_size=25`;
- console.log('Requesting tags from:', apiUrl);
+ // console.log('Requesting tags from:', apiUrl);
// 使用fetchWithRetry获取数据
const data = await fetchWithRetry(apiUrl);
- console.log('Received tags data:', data);
+ // console.log('Received tags data:', data);
currentTagPage = page; // 更新当前页码
if (data.results && data.results.length > 0) {
@@ -1028,6 +1087,7 @@
showToastNotification(`加载标签失败: ${error.message}`, 'error');
}
}
+ window.loadImageTags = loadImageTags;
// 新增: 显示客户端分页控制器
function displayClientPagination(totalPages) {
@@ -1190,7 +1250,7 @@
tableContainer.appendChild(tagTable); // 将表格添加到容器中
// 添加调试信息
- console.log(`显示了 ${tags.length} 个标签`);
+ // console.log(`显示了 ${tags.length} 个标签`);
}
// 新增的排序标签函数
@@ -1375,7 +1435,7 @@
const tagsResults = document.getElementById('tagsResults');
// 保留搜索统计信息
const statsHTML = tagsResults.innerHTML.split('')[0] + '';
- tagsResults.innerHTML = statsHTML + '没有匹配 "' + searchTerm + '" 的TAG
';
+ tagsResults.innerHTML = statsHTML + '没有匹配 "' + searchTerm + '" 的标签
';
}
return; // 已处理全局搜索,不继续执行
@@ -1421,6 +1481,7 @@
noResultsEl.remove();
}
}
+ window.filterTags = filterTags;
// 添加重置搜索功能
function resetTagSearch() {
@@ -1442,6 +1503,7 @@
loadImageTags(currentTagPage);
}
}
+ window.resetTagSearch = resetTagSearch;
// 修改标签搜索容器,添加重置按钮
function enhanceTagSearchContainer() {
@@ -1467,7 +1529,7 @@
// 显示指定的文档
function showDocument(index) {
- console.log('显示文档索引:', index);
+ // console.log('显示文档索引:', index);
if (!window.documentationData || !Array.isArray(window.documentationData)) {
console.error('文档数据不可用');
@@ -1497,7 +1559,7 @@
return;
}
- console.log('文档数据:', doc);
+ // console.log('文档数据:', doc);
// 高亮选中的文档
const docLinks = document.querySelectorAll('.doc-list li a');
@@ -1521,18 +1583,18 @@
// 如果文档内容不存在,则需要获取完整内容
if (!doc.content) {
const docId = doc.id || doc._id;
- console.log('获取文档内容,ID:', docId);
+ // console.log('获取文档内容,ID:', docId);
fetch(`/api/documentation/${docId}`)
.then(response => {
- console.log('文档API响应:', response.status, response.statusText);
+ // console.log('文档API响应:', response.status, response.statusText);
if (!response.ok) {
throw new Error(`获取文档内容失败: ${response.status}`);
}
return response.json();
})
.then(fullDoc => {
- console.log('获取到完整文档:', fullDoc);
+ // console.log('获取到完整文档:', fullDoc);
// 更新缓存的文档内容
window.documentationData[docIndex].content = fullDoc.content;
@@ -1555,15 +1617,13 @@
renderDocumentContent(docContent, doc);
}
}
-
- // 确保showDocument函数在全局范围内可用
window.showDocument = showDocument;
// 渲染文档内容
function renderDocumentContent(container, doc) {
if (!container) return;
- console.log('正在渲染文档:', doc);
+ // console.log('正在渲染文档:', doc);
// 确保有内容可渲染
if (!doc.content && !doc.path) {
@@ -1584,14 +1644,14 @@
// 如果是文件路径但无内容,尝试获取
fetch(`/api/documentation/file?path=${encodeURIComponent(doc.id + '.md')}`)
.then(response => {
- console.log('文件内容响应:', response.status, response.statusText);
+ // console.log('文件内容响应:', response.status, response.statusText);
if (!response.ok) {
throw new Error(`获取文件内容失败: ${response.status}`);
}
return response.text();
})
.then(content => {
- console.log('获取到文件内容,长度:', content.length);
+ // console.log('获取到文件内容,长度:', content.length);
doc.content = content;
renderMarkdownContent(container, doc);
})
@@ -1612,20 +1672,60 @@
function renderMarkdownContent(container, doc) {
if (!container) return;
- console.log('渲染Markdown内容:', doc.title, '内容长度:', doc.content ? doc.content.length : 0);
+ // console.log('渲染Markdown内容:', doc.title, '内容长度:', doc.content ? doc.content.length : 0);
if (doc.content) {
// 使用marked渲染Markdown内容
if (window.marked) {
try {
- const parsedContent = marked.parse(doc.content);
- // 结构:内容(含标题)-> 元数据
- container.innerHTML = `
- ${parsedContent}
-
- ${doc.lastUpdated || doc.updatedAt ? `最后更新: ${new Date(doc.lastUpdated || doc.updatedAt).toLocaleDateString('zh-CN')}` : ''}
-
- `;
+ const rawHtml = marked.parse(doc.content);
+
+ // 创建一个临时的根元素来容纳和处理已解析的Markdown内容
+ const docFragmentRoot = document.createElement('div');
+ docFragmentRoot.innerHTML = rawHtml;
+
+ // 在这个临时根元素中查找所有的 元素
+ const preElements = docFragmentRoot.querySelectorAll('pre');
+ preElements.forEach(preElement => {
+ const codeElement = preElement.querySelector('code');
+ let codeToCopy = '';
+ if (codeElement) {
+ codeToCopy = codeElement.textContent;
+ } else {
+ codeToCopy = preElement.textContent;
+ }
+
+ if (codeToCopy.trim() !== '') {
+ const copyButton = document.createElement('button');
+ copyButton.className = 'copy-btn'; // 应用现有样式
+ copyButton.textContent = '复制';
+ copyButton.onclick = function() { // 事件监听器在此处附加到按钮对象
+ // console.log('[Tutorial Copy Button] Attempting to copy:', codeToCopy); // 保留此调试日志
+ copyToClipboard(codeToCopy, this);
+ };
+ preElement.style.position = 'relative';
+ preElement.appendChild(copyButton); // 按钮被追加到 docFragmentRoot 内的 preElement
+ }
+ });
+
+ // 清空页面上的主容器
+ container.innerHTML = '';
+
+ // 创建 .doc-content div 并将处理过的文档片段追加进去
+ const docContentDiv = document.createElement('div');
+ docContentDiv.className = 'doc-content';
+ // 将 docFragmentRoot 的所有子节点移动到 docContentDiv,以避免多余的包裹 div
+ while (docFragmentRoot.firstChild) {
+ docContentDiv.appendChild(docFragmentRoot.firstChild);
+ }
+ container.appendChild(docContentDiv); // docContentDiv 现在包含带有活动按钮的 PRE 元素
+
+ // 创建并追加 .doc-meta div
+ const docMetaDiv = document.createElement('div');
+ docMetaDiv.className = 'doc-meta';
+ docMetaDiv.innerHTML = `${doc.lastUpdated || doc.updatedAt ? `最后更新: ${new Date(doc.lastUpdated || doc.updatedAt).toLocaleDateString('zh-CN')}` : ''}`;
+ container.appendChild(docMetaDiv);
+
} catch (error) {
console.error('Markdown解析失败:', error);
// 发生错误时,仍然显示原始Markdown内容 + Meta
diff --git a/hubcmdui/web/js/app.js b/hubcmdui/web/js/app.js
index 80b7662..40a42a9 100644
--- a/hubcmdui/web/js/app.js
+++ b/hubcmdui/web/js/app.js
@@ -1,94 +1,94 @@
// 应用程序入口模块
document.addEventListener('DOMContentLoaded', function() {
- console.log('DOM 加载完成,初始化模块...');
+ // console.log('DOM 加载完成,初始化模块...');
// 启动应用程序
core.initApp();
// 在核心应用初始化后,再初始化其他模块
initializeModules();
- console.log('模块初始化已启动');
+ // console.log('模块初始化已启动');
});
// 初始化所有模块
async function initializeModules() {
- console.log('开始初始化所有模块...');
+ // console.log('开始初始化所有模块...');
try {
// 初始化核心模块
- console.log('正在初始化核心模块...');
+ // console.log('正在初始化核心模块...');
if (typeof core !== 'undefined') {
// core.init() 已经在core.initApp()中调用,这里不再重复调用
- console.log('核心模块初始化完成');
+ // console.log('核心模块初始化完成');
} else {
- console.error('核心模块未定义');
+ // console.error('核心模块未定义');
}
// 初始化认证模块
- console.log('正在初始化认证模块...');
+ // console.log('正在初始化认证模块...');
if (typeof auth !== 'undefined') {
await auth.init();
- console.log('认证模块初始化完成');
+ // console.log('认证模块初始化完成');
} else {
- console.error('认证模块未定义');
+ // console.error('认证模块未定义');
}
// 初始化用户中心
- console.log('正在初始化用户中心...');
+ // console.log('正在初始化用户中心...');
if (typeof userCenter !== 'undefined') {
await userCenter.init();
- console.log('用户中心初始化完成');
+ // console.log('用户中心初始化完成');
} else {
- console.error('用户中心未定义');
+ // console.error('用户中心未定义');
}
// 初始化菜单管理
- console.log('正在初始化菜单管理...');
+ // console.log('正在初始化菜单管理...');
if (typeof menuManager !== 'undefined') {
await menuManager.init();
- console.log('菜单管理初始化完成');
+ // console.log('菜单管理初始化完成');
} else {
- console.error('菜单管理未定义');
+ // console.error('菜单管理未定义');
}
// 初始化文档管理
- console.log('正在初始化文档管理...');
+ // console.log('正在初始化文档管理...');
if (typeof documentManager !== 'undefined') {
await documentManager.init();
- console.log('文档管理初始化完成');
+ // console.log('文档管理初始化完成');
} else {
- console.error('文档管理未定义');
+ // console.error('文档管理未定义');
}
// 初始化Docker管理
- console.log('正在初始化Docker管理...');
+ // console.log('正在初始化Docker管理...');
if (typeof dockerManager !== 'undefined') {
await dockerManager.init();
- console.log('Docker管理初始化完成');
+ // console.log('Docker管理初始化完成');
} else {
- console.error('Docker管理未定义');
+ // console.error('Docker管理未定义');
}
// 初始化系统状态
- console.log('正在初始化系统状态...');
+ // console.log('正在初始化系统状态...');
if (typeof systemStatus !== 'undefined') {
if (typeof systemStatus.initDashboard === 'function') {
await systemStatus.initDashboard();
- console.log('系统状态初始化完成');
+ // console.log('系统状态初始化完成');
} else {
- console.error('systemStatus.initDashboard 函数未定义!');
+ // console.error('systemStatus.initDashboard 函数未定义!');
}
} else {
- console.error('系统状态未定义');
+ // console.error('系统状态未定义');
}
// 初始化网络测试
- console.log('正在初始化网络测试...');
+ // console.log('正在初始化网络测试...');
if (typeof networkTest !== 'undefined') {
await networkTest.init();
- console.log('网络测试初始化完成');
+ // console.log('网络测试初始化完成');
} else {
- console.error('网络测试未定义');
+ // console.error('网络测试未定义');
}
// 加载监控配置
@@ -97,31 +97,31 @@ async function initializeModules() {
// 显示默认页面 - 使用core中的showSection函数
core.showSection('dashboard');
- console.log('所有模块初始化完成');
+ // console.log('所有模块初始化完成');
} catch (error) {
- console.error('初始化模块时发生错误:', error);
+ // console.error('初始化模块时发生错误:', error);
// 尝试使用 core.showAlert,如果 core 本身加载失败则用 console.error
if (typeof core !== 'undefined' && core.showAlert) {
core.showAlert('初始化失败: ' + error.message, 'error');
} else {
- console.error('核心模块无法加载,无法显示警告弹窗');
+ // console.error('核心模块无法加载,无法显示警告弹窗');
}
}
}
// 监控配置相关函数
function loadMonitoringConfig() {
- console.log('正在加载监控配置...');
+ // console.log('正在加载监控配置...');
fetch('/api/monitoring-config')
.then(response => {
- console.log('监控配置API响应:', response.status, response.statusText);
+ // console.log('监控配置API响应:', response.status, response.statusText);
if (!response.ok) {
throw new Error(`HTTP状态错误 ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(config => {
- console.log('获取到监控配置:', config);
+ // console.log('获取到监控配置:', config);
// 填充表单
document.getElementById('notificationType').value = config.notificationType || 'wechat';
document.getElementById('webhookUrl').value = config.webhookUrl || '';
@@ -141,10 +141,10 @@ function loadMonitoringConfig() {
document.getElementById('toggleMonitoringBtn').textContent =
config.isEnabled ? '禁用监控' : '启用监控';
- console.log('监控配置加载完成');
+ // console.log('监控配置加载完成');
})
.catch(error => {
- console.error('加载监控配置失败:', error);
+ // console.error('加载监控配置失败:', error);
// 使用安全的方式调用core.showAlert
if (typeof core !== 'undefined' && core && typeof core.showAlert === 'function') {
core.showAlert('加载监控配置失败: ' + error.message, 'error');
@@ -230,7 +230,7 @@ function testNotification() {
});
})
.catch(error => {
- console.error('测试通知失败:', error);
+ // console.error('测试通知失败:', error);
Swal.fire({
icon: 'error',
title: '发送失败',
diff --git a/hubcmdui/web/js/auth.js b/hubcmdui/web/js/auth.js
index f67978b..4b44e27 100644
--- a/hubcmdui/web/js/auth.js
+++ b/hubcmdui/web/js/auth.js
@@ -46,7 +46,7 @@ async function login() {
// 注销函数
async function logout() {
- console.log("注销操作被触发");
+ // console.log("注销操作被触发");
try {
core.showLoading();
const response = await fetch('/api/logout', { method: 'POST' });
@@ -62,7 +62,7 @@ async function logout() {
throw new Error('退出登录失败');
}
} catch (error) {
- console.error('退出登录失败:', error);
+ // console.error('退出登录失败:', error);
core.showAlert('退出登录失败: ' + error.message, 'error');
// 即使API失败也清除本地状态
localStorage.removeItem('isLoggedIn');
@@ -84,7 +84,7 @@ async function refreshCaptcha() {
const data = await response.json();
document.getElementById('captchaText').textContent = data.captcha;
} catch (error) {
- console.error('刷新验证码失败:', error);
+ // console.error('刷新验证码失败:', error);
document.getElementById('captchaText').textContent = '验证码加载失败,点击重试';
}
}
@@ -110,7 +110,7 @@ function showLoginModal() {
// 导出模块
const auth = {
init: function() {
- console.log('初始化认证模块...');
+ // console.log('初始化认证模块...');
// 在这里可以添加认证模块初始化的相关代码
return Promise.resolve(); // 返回一个已解决的 Promise,保持与其他模块一致
},
diff --git a/hubcmdui/web/js/core.js b/hubcmdui/web/js/core.js
index c1af5e3..f3765fb 100644
--- a/hubcmdui/web/js/core.js
+++ b/hubcmdui/web/js/core.js
@@ -13,21 +13,21 @@ let systemConfig = {};
* 检查登录状态,加载基础配置
*/
async function initApp() {
- console.log('初始化应用...');
- console.log('-------------调试信息开始-------------');
- console.log('当前URL:', window.location.href);
- console.log('浏览器信息:', navigator.userAgent);
- console.log('DOM已加载状态:', document.readyState);
+ // console.log('初始化应用...');
+ // console.log('-------------调试信息开始-------------');
+ // console.log('当前URL:', window.location.href);
+ // console.log('浏览器信息:', navigator.userAgent);
+ // console.log('DOM已加载状态:', document.readyState);
// 检查当前页面是否为登录页
const isLoginPage = window.location.pathname.includes('admin');
- console.log('是否为管理页面:', isLoginPage);
+ // console.log('是否为管理页面:', isLoginPage);
try {
// 检查会话状态
const sessionResult = await checkSession();
const isAuthenticated = sessionResult.authenticated;
- console.log('会话检查结果:', isAuthenticated);
+ // console.log('会话检查结果:', isAuthenticated);
// 检查localStorage中的登录状态 (主要用于刷新页面时保持UI)
const localLoginState = localStorage.getItem('isLoggedIn') === 'true';
@@ -40,12 +40,12 @@ async function initApp() {
if (isLoginPage) {
// 在登录页,但会话有效,显示管理界面
- console.log('已登录,显示管理界面...');
+ // console.log('已登录,显示管理界面...');
await loadSystemConfig();
showAdminInterface();
} else {
// 在非登录页,正常显示
- console.log('已登录,继续应用初始化...');
+ // console.log('已登录,继续应用初始化...');
await loadSystemConfig();
showAdminInterface(); // 确保管理界面可见
}
@@ -56,27 +56,27 @@ async function initApp() {
if (!isLoginPage) {
// 在非登录页,重定向到登录页
- console.log('未登录,重定向到登录页...');
+ // console.log('未登录,重定向到登录页...');
window.location.href = '/admin';
return false;
} else {
// 在登录页,显示登录框
- console.log('未登录,显示登录模态框...');
+ // console.log('未登录,显示登录模态框...');
hideLoadingIndicator();
showLoginModal();
}
}
- console.log('应用初始化完成');
- console.log('-------------调试信息结束-------------');
+ // console.log('应用初始化完成');
+ // console.log('-------------调试信息结束-------------');
return isAuthenticated;
} catch (error) {
- console.error('初始化应用失败:', error);
- console.log('-------------调试错误信息-------------');
- console.log('错误堆栈:', error.stack);
- console.log('错误类型:', error.name);
- console.log('错误消息:', error.message);
- console.log('---------------------------------------');
+ // console.error('初始化应用失败:', error);
+ // console.log('-------------调试错误信息-------------');
+ // console.log('错误堆栈:', error.stack);
+ // console.log('错误类型:', error.name);
+ // console.log('错误消息:', error.message);
+ // console.log('---------------------------------------');
showAlert('加载应用失败:' + error.message, 'error');
hideLoadingIndicator();
showLoginModal();
@@ -111,7 +111,7 @@ async function checkSession() {
authenticated: false
};
} catch (error) {
- console.error('检查会话状态出错:', error);
+ // console.error('检查会话状态出错:', error);
return {
authenticated: false,
error: error.message
@@ -133,12 +133,12 @@ function loadSystemConfig() {
return response.json();
})
.then(config => {
- console.log('加载配置成功:', config);
+ // console.log('加载配置成功:', config);
// 应用配置
applySystemConfig(config);
})
.catch(error => {
- console.error('加载配置失败:', error);
+ // console.error('加载配置失败:', error);
showAlert('加载配置失败: ' + error.message, 'warning');
});
}
@@ -157,18 +157,18 @@ function applySystemConfig(config) {
* 显示管理界面
*/
function showAdminInterface() {
- console.log('开始显示管理界面...');
+ // console.log('开始显示管理界面...');
hideLoadingIndicator();
const adminContainer = document.getElementById('adminContainer');
if (adminContainer) {
- console.log('找到管理界面容器,设置为显示');
+ // console.log('找到管理界面容器,设置为显示');
adminContainer.style.display = 'flex';
} else {
- console.error('未找到管理界面容器元素 #adminContainer');
+ // console.error('未找到管理界面容器元素 #adminContainer');
}
- console.log('管理界面已显示,正在初始化事件监听器');
+ // console.log('管理界面已显示,正在初始化事件监听器');
// 初始化菜单事件监听
initEventListeners();
@@ -178,13 +178,13 @@ function showAdminInterface() {
* 隐藏加载提示器
*/
function hideLoadingIndicator() {
- console.log('正在隐藏加载提示器...');
+ // console.log('正在隐藏加载提示器...');
const loadingIndicator = document.getElementById('loadingIndicator');
if (loadingIndicator) {
loadingIndicator.style.display = 'none';
- console.log('加载提示器已隐藏');
+ // console.log('加载提示器已隐藏');
} else {
- console.warn('未找到加载提示器元素 #loadingIndicator');
+ // console.warn('未找到加载提示器元素 #loadingIndicator');
}
}
@@ -331,54 +331,47 @@ function throttle(func, wait = 300) {
* 初始化事件监听
*/
function initEventListeners() {
- console.log('开始初始化事件监听器...');
+ // console.log('开始初始化事件监听器...');
- // 侧边栏菜单切换事件
+ // 侧边栏菜单项事件
const menuItems = document.querySelectorAll('.sidebar-nav li');
- console.log('找到侧边栏菜单项数量:', menuItems.length);
-
- if (menuItems.length > 0) {
- menuItems.forEach((item, index) => {
- const sectionId = item.getAttribute('data-section');
- console.log(`绑定事件到菜单项 #${index+1}: ${sectionId}`);
- item.addEventListener('click', function() {
- const sectionId = this.getAttribute('data-section');
- showSection(sectionId);
- });
- });
- console.log('侧边栏菜单事件监听器已绑定');
- } else {
- console.error('未找到侧边栏菜单项 .sidebar-nav li');
- }
+ // console.log('找到侧边栏菜单项数量:', menuItems.length);
+ menuItems.forEach((item, index) => {
+ const section = item.getAttribute('data-section');
+ // console.log(`绑定事件到菜单项 #${index + 1}: ${section}`);
+ item.addEventListener('click', () => showSection(section));
+ });
+ // console.log('侧边栏菜单事件监听器已绑定');
// 用户中心按钮
const userCenterBtn = document.getElementById('userCenterBtn');
if (userCenterBtn) {
- console.log('找到用户中心按钮,绑定事件');
+ // console.log('找到用户中心按钮,绑定事件');
userCenterBtn.addEventListener('click', () => showSection('user-center'));
- } else {
- console.warn('未找到用户中心按钮 #userCenterBtn');
}
-
- // 登出按钮
+
+ // 登出按钮 (侧边栏)
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
- console.log('找到登出按钮,绑定事件');
- logoutBtn.addEventListener('click', () => auth.logout());
- } else {
- console.warn('未找到登出按钮 #logoutBtn');
+ // console.log('找到登出按钮,绑定事件');
+ logoutBtn.addEventListener('click', () => {
+ if (window.auth && typeof window.auth.logout === 'function') {
+ window.auth.logout();
+ }
+ });
}
-
- // 用户中心内登出按钮
+
+ // 登出按钮 (用户中心)
const ucLogoutBtn = document.getElementById('ucLogoutBtn');
if (ucLogoutBtn) {
- console.log('找到用户中心内登出按钮,绑定事件');
- ucLogoutBtn.addEventListener('click', () => auth.logout());
- } else {
- console.warn('未找到用户中心内登出按钮 #ucLogoutBtn');
+ // console.log('找到用户中心内登出按钮,绑定事件');
+ ucLogoutBtn.addEventListener('click', () => {
+ if (window.auth && typeof window.auth.logout === 'function') {
+ window.auth.logout();
+ }
+ });
}
-
- console.log('事件监听器初始化完成');
+ // console.log('事件监听器初始化完成');
}
/**
@@ -386,77 +379,108 @@ function initEventListeners() {
* @param {string} sectionId 要显示的内容区域ID
*/
function showSection(sectionId) {
- console.log(`尝试显示内容区域: ${sectionId}`);
-
- // 获取所有内容区域和菜单项
+ // console.log('尝试显示内容区域:', sectionId);
+
const contentSections = document.querySelectorAll('.content-section');
const menuItems = document.querySelectorAll('.sidebar-nav li');
-
- console.log(`找到 ${contentSections.length} 个内容区域和 ${menuItems.length} 个菜单项`);
-
+ // console.log('找到', contentSections.length, '个内容区域和', menuItems.length, '个菜单项');
+
let sectionFound = false;
- let menuItemFound = false;
-
- // 隐藏所有内容区域,取消激活所有菜单项
contentSections.forEach(section => {
- section.classList.remove('active');
+ if (section.id === sectionId) {
+ section.classList.add('active');
+ sectionFound = true;
+ // console.log('成功激活内容区域:', sectionId);
+ } else {
+ section.classList.remove('active');
+ }
});
-
+
+ if (!sectionFound) {
+ // console.warn('未找到要显示的内容区域:', sectionId, '. 默认显示dashboard.');
+ document.getElementById('dashboard').classList.add('active');
+ sectionId = 'dashboard'; // 更新 sectionId 以便正确高亮菜单
+ }
+
+ let menuItemFound = false;
menuItems.forEach(item => {
- item.classList.remove('active');
+ if (item.getAttribute('data-section') === sectionId) {
+ item.classList.add('active');
+ menuItemFound = true;
+ // console.log('成功激活菜单项:', sectionId);
+ } else {
+ item.classList.remove('active');
+ }
});
-
- // 激活指定的内容区域
- const targetSection = document.getElementById(sectionId);
- if (targetSection) {
- targetSection.classList.add('active');
- sectionFound = true;
- console.log(`成功激活内容区域: ${sectionId}`);
-
- // 特殊处理:切换到用户中心时,确保用户信息已加载
- if (sectionId === 'user-center' && window.userCenter) {
- console.log('切换到用户中心,调用 getUserInfo()');
- window.userCenter.getUserInfo();
- }
- } else {
- console.error(`未找到指定的内容区域: ${sectionId}`);
+
+ if (!menuItemFound) {
+ // console.warn('未找到要激活的菜单项 for section:', sectionId);
+ // 尝试激活 dashboard 作为回退
+ const dashboardItem = document.querySelector('.sidebar-nav li[data-section="dashboard"]');
+ if(dashboardItem) dashboardItem.classList.add('active');
}
-
- // 激活相应的菜单项
- const targetMenuItem = document.querySelector(`.sidebar-nav li[data-section="${sectionId}"]`);
- if (targetMenuItem) {
- targetMenuItem.classList.add('active');
- menuItemFound = true;
- console.log(`成功激活菜单项: ${sectionId}`);
- } else {
- console.error(`未找到对应的菜单项: ${sectionId}`);
- }
-
- // 如果没有找到指定的内容区域和菜单项,显示仪表盘
- if (!sectionFound && !menuItemFound) {
- console.warn(`未找到指定的内容区域和菜单项,将显示默认仪表盘`);
- const dashboard = document.getElementById('dashboard');
- if (dashboard) {
- dashboard.classList.add('active');
- const dashboardMenuItem = document.querySelector('.sidebar-nav li[data-section="dashboard"]');
- if (dashboardMenuItem) {
- dashboardMenuItem.classList.add('active');
- }
- }
- }
-
- // 切换内容区域后可能需要执行的额外操作
+
+ // 更新 URL hash
+ window.location.hash = sectionId;
+
+ // 刷新特定部分的内容,例如仪表盘
if (sectionId === 'dashboard') {
- console.log('已激活仪表盘,无需再次刷新系统状态');
- // 不再自动刷新系统状态,仅在首次加载或用户手动点击刷新按钮时刷新
+ // console.log('已激活仪表盘,无需再次刷新系统状态');
+ // if (window.systemStatus && typeof window.systemStatus.refreshSystemStatus === 'function') {
+ // window.systemStatus.refreshSystemStatus();
+ // }
+ }
+ // console.log('内容区域切换完成:', sectionId);
+}
+
+/**
+ * 根据URL hash显示对应的区域
+ */
+function showSectionFromHash() {
+ const hash = window.location.hash.substring(1); // 移除 '#' 号
+ if (hash) {
+ // console.log('从URL Hash加载区域:', hash);
+ showSection(hash);
+ } else {
+ // console.log('URL Hash为空,默认显示dashboard区域');
+ showSection('dashboard'); // 默认显示仪表盘
+ }
+}
+
+/**
+ * 切换加载状态
+ * @param {boolean} isLoading - 是否正在加载
+ * @param {string} elementId - 目标元素ID
+ * @param {string} originalText - 原始文本(可选)
+ */
+function toggleLoadingState(isLoading, elementId, originalText = null) {
+ const element = document.getElementById(elementId);
+ if (!element) {
+ // console.warn(`未找到元素 ${elementId} 来切换加载状态`);
+ return;
+ }
+
+ if (isLoading) {
+ element.disabled = true;
+ // 可以考虑保存原始文本,如果按钮文本被修改了
+ if (originalText && !element.dataset.originalText) {
+ element.dataset.originalText = element.innerHTML;
+ }
+ element.innerHTML = ' 加载中...';
+ } else {
+ element.disabled = false;
+ if (element.dataset.originalText) {
+ element.innerHTML = element.dataset.originalText;
+ } else if (originalText) { // 如果没有保存的原始文本,但传入了,也使用
+ element.innerHTML = originalText;
+ }
+ // 如果按钮文本没有被修改为 "加载中...",则不需要恢复
}
-
- console.log(`内容区域切换完成: ${sectionId}`);
}
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', function() {
- console.log('DOM已加载,正在初始化应用...');
+ // console.log('DOM已加载,正在初始化应用...');
initApp();
// 检查URL参数,处理消息提示等
@@ -475,25 +499,22 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
-// 导出核心对象
-const core = {
- isLoggedIn,
+// 导出核心函数和变量 (如果需要在其他模块直接使用)
+window.core = {
initApp,
checkSession,
loadSystemConfig,
- applySystemConfig,
- showLoading,
- hideLoading,
+ showAdminInterface,
hideLoadingIndicator,
showLoginModal,
showAlert,
showConfirm,
+ showLoading,
+ hideLoading,
+ showSection,
+ showSectionFromHash,
formatDateTime,
debounce,
throttle,
- initEventListeners,
- showSection
+ toggleLoadingState
};
-
-// 全局公开核心模块
-window.core = core;
diff --git a/hubcmdui/web/js/documentManager.js b/hubcmdui/web/js/documentManager.js
index 261aab9..de11890 100644
--- a/hubcmdui/web/js/documentManager.js
+++ b/hubcmdui/web/js/documentManager.js
@@ -13,12 +13,12 @@ let editorMd = null;
const documentManager = {
// 初始化文档管理
init: function() {
- console.log('初始化文档管理模块...');
+ // console.log('初始化文档管理模块...');
// 渲染表头
this.renderDocumentTableHeader();
// 加载文档列表
return this.loadDocuments().catch(err => {
- console.error('加载文档列表失败:', err);
+ // console.error('加载文档列表失败:', err);
return Promise.resolve(); // 即使失败也继续初始化过程
});
},
@@ -28,7 +28,7 @@ const documentManager = {
try {
const documentTable = document.getElementById('documentTable');
if (!documentTable) {
- console.warn('文档管理表格元素 (id=\"documentTable\") 未找到,无法渲染表头。');
+ // console.warn('文档管理表格元素 (id=\"documentTable\") 未找到,无法渲染表头。');
return;
}
@@ -37,7 +37,7 @@ const documentManager = {
if (!thead) {
thead = document.createElement('thead');
documentTable.insertBefore(thead, documentTable.firstChild); // 确保 thead 在 tbody 之前
- console.log('创建了文档表格的 thead 元素。');
+ // console.log('创建了文档表格的 thead 元素。');
}
// 设置表头内容 (包含 ID 列)
@@ -51,9 +51,9 @@ const documentManager = {
操作
`;
- console.log('文档表格表头已渲染。');
+ // console.log('文档表格表头已渲染。');
} catch (error) {
- console.error('渲染文档表格表头时出错:', error);
+ // console.error('渲染文档表格表头时出错:', error);
}
},
@@ -78,11 +78,11 @@ const documentManager = {
});
if (sessionResponse.status === 401) {
- console.warn('会话已过期,无法加载文档');
+ // console.warn('会话已过期,无法加载文档');
sessionValid = false;
}
} catch (sessionError) {
- console.warn('检查会话状态发生网络错误:', sessionError);
+ // console.warn('检查会话状态发生网络错误:', sessionError);
// 发生网络错误时继续尝试加载文档
}
@@ -98,7 +98,7 @@ const documentManager = {
for (const path of possiblePaths) {
try {
- console.log(`尝试从 ${path} 获取文档列表`);
+ // console.log(`尝试从 ${path} 获取文档列表`);
const response = await fetch(path, {
credentials: 'same-origin',
headers: {
@@ -108,7 +108,7 @@ const documentManager = {
});
if (response.status === 401) {
- console.warn(`API路径 ${path} 返回未授权状态`);
+ // console.warn(`API路径 ${path} 返回未授权状态`);
authError = true;
continue;
}
@@ -138,18 +138,18 @@ const documentManager = {
// 先渲染表头,再渲染文档列表
this.renderDocumentTableHeader();
this.renderDocumentList();
- console.log(`成功从API路径 ${path} 加载文档列表`, documents);
+ // console.log(`成功从API路径 ${path} 加载文档列表`, documents);
success = true;
break;
}
} catch (e) {
- console.warn(`从 ${path} 加载文档失败:`, e);
+ // console.warn(`从 ${path} 加载文档失败:`, e);
}
}
// 处理认证错误 - 只有当会话检查和API请求都明确失败时才强制登出
if ((authError || !sessionValid) && !success && localStorage.getItem('isLoggedIn') === 'true') {
- console.warn('会话检查和API请求均指示会话已过期');
+ // console.warn('会话检查和API请求均指示会话已过期');
core.showAlert('会话已过期,请重新登录', 'warning');
setTimeout(() => {
localStorage.removeItem('isLoggedIn');
@@ -160,14 +160,14 @@ const documentManager = {
// 如果API请求失败但不是认证错误,显示空文档列表
if (!success) {
- console.log('API请求失败,显示空文档列表');
+ // console.log('API请求失败,显示空文档列表');
documents = [];
// 仍然需要渲染表头
this.renderDocumentTableHeader();
this.renderDocumentList();
}
} catch (error) {
- console.error('加载文档失败:', error);
+ // console.error('加载文档失败:', error);
// 在UI上显示错误
const documentTableBody = document.getElementById('documentTableBody');
if (documentTableBody) {
@@ -184,16 +184,16 @@ const documentManager = {
try {
const editorContainer = document.getElementById('editor');
if (!editorContainer) {
- console.error('找不到编辑器容器元素');
+ // console.error('找不到编辑器容器元素');
return;
}
// 检查 toastui 是否已加载
- console.log('检查编辑器依赖项:', typeof toastui);
+ // console.log('检查编辑器依赖项:', typeof toastui);
// 确保 toastui 对象存在
if (typeof toastui === 'undefined') {
- console.error('Toast UI Editor 未加载');
+ // console.error('Toast UI Editor 未加载');
return;
}
@@ -213,9 +213,9 @@ const documentManager = {
]
});
- console.log('编辑器初始化完成', editorMd);
+ // console.log('编辑器初始化完成', editorMd);
} catch (error) {
- console.error('初始化编辑器出错:', error);
+ // console.error('初始化编辑器出错:', error);
core.showAlert('初始化编辑器失败: ' + error.message, 'error');
}
},
@@ -292,11 +292,11 @@ const documentManager = {
});
if (sessionResponse.status === 401) {
- console.warn('会话已过期,无法保存文档');
+ // console.warn('会话已过期,无法保存文档');
sessionValid = false;
}
} catch (sessionError) {
- console.warn('检查会话状态发生网络错误:', sessionError);
+ // console.warn('检查会话状态发生网络错误:', sessionError);
// 发生网络错误时继续尝试保存操作
}
@@ -326,7 +326,7 @@ const documentManager = {
const method = currentDocument && currentDocument.id ? 'PUT' : 'POST';
- console.log(`尝试${method === 'PUT' ? '更新' : '创建'}文档,标题: ${title}`);
+ // console.log(`尝试${method === 'PUT' ? '更新' : '创建'}文档,标题: ${title}`);
const response = await fetch(apiUrl, {
method: method,
@@ -345,7 +345,7 @@ const documentManager = {
// 处理响应
if (response.status === 401) {
// 明确的未授权响应
- console.warn('保存文档返回401未授权');
+ // console.warn('保存文档返回401未授权');
core.showAlert('未登录或会话已过期,请重新登录', 'warning');
setTimeout(() => {
localStorage.removeItem('isLoggedIn');
@@ -367,7 +367,7 @@ const documentManager = {
}
const savedDoc = await response.json();
- console.log('保存的文档:', savedDoc);
+ // console.log('保存的文档:', savedDoc);
// 确保savedDoc包含必要的时间字段
if (savedDoc) {
@@ -387,11 +387,11 @@ const documentManager = {
createdAt: fullDoc.createdAt,
updatedAt: fullDoc.updatedAt
});
- console.log('获取到完整的文档时间信息:', fullDoc);
+ // console.log('获取到完整的文档时间信息:', fullDoc);
}
}
} catch (timeError) {
- console.warn('获取文档完整时间信息失败:', timeError);
+ // console.warn('获取文档完整时间信息失败:', timeError);
}
}
@@ -408,7 +408,7 @@ const documentManager = {
this.hideEditor();
await this.loadDocuments(); // 重新加载文档列表
} catch (error) {
- console.error('保存文档失败:', error);
+ // console.error('保存文档失败:', error);
core.showAlert('保存文档失败: ' + error.message, 'error');
} finally {
core.hideLoading();
@@ -470,7 +470,7 @@ const documentManager = {
updatedAt = '未更新';
}
} catch (error) {
- console.warn(`解析文档时间失败:`, error, doc);
+ // console.warn(`解析文档时间失败:`, error, doc);
}
const statusClasses = doc.published ? 'status-badge status-running' : 'status-badge status-stopped';
@@ -505,10 +505,10 @@ const documentManager = {
// 编辑文档
editDocument: async function(id) {
try {
- console.log(`准备编辑文档,ID: ${id}`);
- core.showLoading();
+ // console.log(`准备编辑文档,ID: ${id}`);
+ core.showLoading('加载文档中...'); // 更明确的加载提示
- // 检查会话状态,优化会话检查逻辑
+ // 会话检查逻辑保持不变
let sessionValid = true;
try {
const sessionResponse = await fetch('/api/check-session', {
@@ -518,183 +518,67 @@ const documentManager = {
},
credentials: 'same-origin'
});
-
- if (sessionResponse.status === 401) {
- console.warn('会话已过期,无法编辑文档');
- sessionValid = false;
- }
- } catch (sessionError) {
- console.warn('检查会话状态发生网络错误:', sessionError);
- // 发生网络错误时不立即判定会话失效,继续尝试编辑操作
+ if (sessionResponse.status === 401) sessionValid = false;
+ } catch (sessionError) {
+ // console.warn('检查会话状态发生网络错误:', sessionError);
+ // 继续尝试,API调用时会再次处理401
}
- // 只有在明确会话无效时才提示重新登录
if (!sessionValid) {
core.showAlert('您的会话已过期,请重新登录', 'warning');
- setTimeout(() => {
- localStorage.removeItem('isLoggedIn');
- window.location.reload();
- }, 1500);
+ auth.showLoginModal(); // 使用 auth 模块显示登录
+ core.hideLoading();
return;
}
- // 在本地查找文档
- currentDocument = documents.find(doc => doc.id === id);
+ // 不再依赖本地缓存的列表项获取content,始终从API获取完整文档
+ // console.log('始终从API获取完整文档详情进行编辑,ID:', id);
- // 如果本地未找到,从API获取
- if (!currentDocument && id) {
- try {
- console.log('从API获取文档详情');
-
- // 尝试多个可能的API路径
- const apiPaths = [
- `/api/documents/${id}`,
- `/api/documentation/${id}`
- ];
-
- let docResponse = null;
- let authError = false;
-
- for (const apiPath of apiPaths) {
- try {
- console.log(`尝试从 ${apiPath} 获取文档`);
- const response = await fetch(apiPath, {
- credentials: 'same-origin',
- headers: {
- 'X-Requested-With': 'XMLHttpRequest'
- }
- });
-
- // 只有明确401错误才认定为会话过期
- if (response.status === 401) {
- console.warn(`API ${apiPath} 返回401未授权`);
- authError = true;
- continue;
- }
-
- if (response.ok) {
- docResponse = response;
- console.log(`成功从 ${apiPath} 获取文档`);
- break;
- }
- } catch (pathError) {
- console.warn(`从 ${apiPath} 获取文档失败:`, pathError);
- }
- }
-
- // 处理认证错误
- if (authError && !docResponse) {
- core.showAlert('未登录或会话已过期,请重新登录', 'warning');
- setTimeout(() => {
- localStorage.removeItem('isLoggedIn');
- window.location.reload();
- }, 1500);
- return;
- }
-
- if (docResponse && docResponse.ok) {
- currentDocument = await docResponse.json();
- console.log('获取到文档详情:', currentDocument);
-
- // 确保文档包含必要的时间字段
- if (!currentDocument.createdAt && currentDocument.updatedAt) {
- // 如果没有创建时间但有更新时间,使用更新时间
- currentDocument.createdAt = currentDocument.updatedAt;
- } else if (!currentDocument.createdAt) {
- // 如果都没有,使用当前时间
- currentDocument.createdAt = new Date().toISOString();
- }
-
- if (!currentDocument.updatedAt) {
- // 如果没有更新时间,使用创建时间
- currentDocument.updatedAt = currentDocument.createdAt;
- }
-
- // 将获取到的文档添加到文档列表中
- const existingIndex = documents.findIndex(d => d.id === id);
- if (existingIndex >= 0) {
- documents[existingIndex] = currentDocument;
- } else {
- documents.push(currentDocument);
- }
- } else {
- throw new Error('所有API路径都无法获取文档');
- }
- } catch (apiError) {
- console.error('从API获取文档详情失败:', apiError);
- core.showAlert('获取文档详情失败: ' + apiError.message, 'error');
- }
- }
-
- // 如果仍然没有找到文档,显示错误
- if (!currentDocument) {
- core.showAlert('未找到指定的文档', 'error');
+ const response = await fetch(`/api/documents/${id}`, {
+ headers: {
+ 'Cache-Control': 'no-cache', // 确保获取最新数据
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ credentials: 'same-origin'
+ });
+
+ if (response.status === 401) {
+ core.showAlert('您的会话已过期或无权限访问此文档,请重新登录', 'warning');
+ auth.showLoginModal();
+ core.hideLoading();
return;
}
-
- // 显示编辑器界面并设置内容
- this.showEditor();
-
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ // console.error(`获取文档失败 (${response.status}):`, errorText);
+ throw new Error(`获取文档内容失败: ${errorText || response.status}`);
+ }
+
+ const docToEdit = await response.json();
+ currentDocument = docToEdit; // 更新当前编辑的文档对象
+
// 确保编辑器已初始化
if (!editorMd) {
- await new Promise(resolve => setTimeout(resolve, 100));
this.initEditor();
- await new Promise(resolve => setTimeout(resolve, 500));
+ // 等待编辑器初始化完成后再继续
+ // 使用短延时确保编辑器DOM完全准备好
+ await new Promise(resolve => setTimeout(resolve, 100));
}
- // 设置文档内容
- if (editorMd) {
- document.getElementById('documentTitle').value = currentDocument.title || '';
-
- if (currentDocument.content) {
- console.log(`设置文档内容,长度: ${currentDocument.content.length}`);
- editorMd.setMarkdown(currentDocument.content);
- } else {
- console.log('文档内容为空,尝试额外获取内容');
-
- // 如果文档内容为空,尝试额外获取内容
- try {
- const contentResponse = await fetch(`/api/documents/${id}/content`, {
- credentials: 'same-origin',
- headers: {
- 'X-Requested-With': 'XMLHttpRequest'
- }
- });
-
- // 只有明确401错误才提示重新登录
- if (contentResponse.status === 401) {
- core.showAlert('会话已过期,请重新登录', 'warning');
- setTimeout(() => {
- localStorage.removeItem('isLoggedIn');
- window.location.reload();
- }, 1500);
- return;
- }
-
- if (contentResponse.ok) {
- const contentData = await contentResponse.json();
- if (contentData.content) {
- currentDocument.content = contentData.content;
- editorMd.setMarkdown(contentData.content);
- console.log('成功获取额外内容');
- }
- }
- } catch (contentError) {
- console.warn('获取额外内容失败:', contentError);
- }
-
- // 如果仍然没有内容,设置为空
- if (!currentDocument.content) {
- editorMd.setMarkdown('');
- }
- }
- } else {
- console.error('编辑器初始化失败,无法设置内容');
- core.showAlert('编辑器初始化失败,请刷新页面重试', 'error');
+ if (!editorMd) {
+ core.showAlert('编辑器初始化失败,无法编辑文档。', 'error');
+ core.hideLoading();
+ return;
}
+
+ document.getElementById('documentTitle').value = docToEdit.title || '';
+ editorMd.setMarkdown(docToEdit.content || '');
+ this.showEditor();
+
} catch (error) {
- console.error('编辑文档时出错:', error);
- core.showAlert('编辑文档失败: ' + error.message, 'error');
+ // console.error('编辑文档时出错:', error);
+ core.showAlert(`加载文档进行编辑失败: ${error.message}`, 'error');
} finally {
core.hideLoading();
}
@@ -736,7 +620,7 @@ const documentManager = {
}).then(async (result) => {
if (result.isConfirmed) {
try {
- console.log(`尝试删除文档: ${id}`);
+ // console.log(`尝试删除文档: ${id}`);
// 检查会话状态
const sessionResponse = await fetch('/api/check-session', {
@@ -778,11 +662,11 @@ const documentManager = {
throw new Error(errorData.error || '删除文档失败');
}
- console.log('文档删除成功响应:', await response.json());
+ // console.log('文档删除成功响应:', await response.json());
core.showAlert('文档已成功删除', 'success');
await this.loadDocuments(); // 重新加载文档列表
} catch (error) {
- console.error('删除文档失败:', error);
+ // console.error('删除文档失败:', error);
core.showAlert('删除文档失败: ' + error.message, 'error');
}
}
@@ -815,7 +699,7 @@ const documentManager = {
return;
}
- console.log(`尝试切换文档 ${id} 的发布状态,当前状态:`, doc.published);
+ // console.log(`尝试切换文档 ${id} 的发布状态,当前状态:`, doc.published);
// 构建更新请求数据
const updateData = {
@@ -852,7 +736,7 @@ const documentManager = {
// 更新成功
const updatedDoc = await response.json();
- console.log('文档状态更新响应:', updatedDoc);
+ // console.log('文档状态更新响应:', updatedDoc);
// 更新本地文档列表
const docIndex = documents.findIndex(d => d.id === id);
@@ -863,7 +747,7 @@ const documentManager = {
core.showAlert('文档状态已更新', 'success');
} catch (error) {
- console.error('更改发布状态失败:', error);
+ // console.error('更改发布状态失败:', error);
core.showAlert('更改发布状态失败: ' + error.message, 'error');
} finally {
core.hideLoading();
@@ -880,7 +764,7 @@ window.documentManager = documentManager;
*/
async function showDocument(docId) {
try {
- console.log('正在获取文档内容,ID:', docId);
+ // console.log('正在获取文档内容,ID:', docId);
// 显示加载状态
const documentContent = document.getElementById('documentContent');
@@ -895,7 +779,7 @@ async function showDocument(docId) {
}
const doc = await response.json();
- console.log('获取到文档:', doc);
+ // console.log('获取到文档:', doc);
// 更新文档内容区域
if (documentContent) {
@@ -916,13 +800,13 @@ async function showDocument(docId) {
`;
}
} else {
- console.error('找不到文档内容容器,ID: documentContent');
+ // console.error('找不到文档内容容器,ID: documentContent');
}
// 高亮当前选中的文档
highlightSelectedDocument(docId);
} catch (error) {
- console.error('获取文档内容失败:', error);
+ // console.error('获取文档内容失败:', error);
// 显示错误信息
const documentContent = document.getElementById('documentContent');
diff --git a/hubcmdui/web/js/menuManager.js b/hubcmdui/web/js/menuManager.js
index 76d416a..693ad5f 100644
--- a/hubcmdui/web/js/menuManager.js
+++ b/hubcmdui/web/js/menuManager.js
@@ -10,7 +10,7 @@ let currentConfig = {}; // 保存当前完整的配置
const menuManager = {
// 初始化菜单管理
init: async function() {
- console.log('初始化菜单管理 (config.json)...');
+ // console.log('初始化菜单管理 (config.json)...');
this.renderMenuTableHeader(); // 渲染表头
await this.loadMenuItems(); // 加载菜单项
return Promise.resolve();
@@ -54,10 +54,10 @@ const menuManager = {
configMenuItems = currentConfig.menuItems || []; // 提取菜单项,如果不存在则为空数组
this.renderMenuItems();
- console.log('成功从 /api/config 加载菜单项', configMenuItems);
+ // console.log('成功从 /api/config 加载菜单项', configMenuItems);
} catch (error) {
- console.error('加载菜单项失败:', error);
+ // console.error('加载菜单项失败:', error);
const menuTableBody = document.getElementById('menuTableBody');
if (menuTableBody) {
menuTableBody.innerHTML = `
@@ -83,7 +83,7 @@ const menuManager = {
menuTableBody.innerHTML = ''; // 清空现有内容
if (!Array.isArray(configMenuItems)) {
- console.error("configMenuItems 不是一个数组:", configMenuItems);
+ // console.error("configMenuItems 不是一个数组:", configMenuItems);
menuTableBody.innerHTML = '菜单数据格式错误 ';
return;
}
@@ -114,26 +114,37 @@ const menuManager = {
const menuTableBody = document.getElementById('menuTableBody');
if (!menuTableBody) return;
+ // 如果已存在新行,则不重复添加
+ if (document.getElementById('new-menu-item-row')) {
+ document.getElementById('new-text').focus();
+ return;
+ }
+
const newRow = document.createElement('tr');
newRow.id = 'new-menu-item-row';
- newRow.className = 'new-item-row';
+ newRow.className = 'new-item-row'; // 可以为此行添加特定样式
newRow.innerHTML = `
#
-
-
+
+
-
-
-
-
+
+
+
`;
- menuTableBody.appendChild(newRow);
+ // 将新行添加到表格体的最上方
+ menuTableBody.insertBefore(newRow, menuTableBody.firstChild);
document.getElementById('new-text').focus();
},
@@ -174,7 +185,7 @@ const menuManager = {
core.showAlert('菜单项已添加', 'success');
} catch (error) {
- console.error('添加菜单项失败:', error);
+ // console.error('添加菜单项失败:', error);
core.showAlert('添加菜单项失败: ' + error.message, 'error');
}
},
@@ -505,7 +516,7 @@ const menuManager = {
});
} catch (error) {
- console.error('更新菜单项失败:', error);
+ // console.error('更新菜单项失败:', error);
Swal.fire({
icon: 'error',
title: '保存失败',
@@ -562,7 +573,7 @@ const menuManager = {
core.showAlert('菜单项已删除', 'success');
} catch (error) {
- console.error('删除菜单项失败:', error);
+ // console.error('删除菜单项失败:', error);
core.showAlert('删除菜单项失败: ' + error.message, 'error');
}
});
diff --git a/hubcmdui/web/js/nav-menu.js b/hubcmdui/web/js/nav-menu.js
index 33372b9..1df38e2 100644
--- a/hubcmdui/web/js/nav-menu.js
+++ b/hubcmdui/web/js/nav-menu.js
@@ -8,14 +8,14 @@ let menuLoaded = false;
// 立即执行初始化函数
(function() {
- console.log('[菜单模块] 初始化开始');
+ // console.log('[菜单模块] 初始化开始');
try {
// 页面加载完成后执行,但不在这里调用加载函数
document.addEventListener('DOMContentLoaded', function() {
- console.log('[菜单模块] DOM内容加载完成,等待loadMenu或loadNavMenu调用');
+ // console.log('[菜单模块] DOM内容加载完成,等待loadMenu或loadNavMenu调用');
// 不在这里调用,避免重复加载
});
- console.log('[菜单模块] 初始化完成,等待调用');
+ // console.log('[菜单模块] 初始化完成,等待调用');
} catch (error) {
console.error('[菜单模块] 初始化失败:', error);
}
@@ -24,11 +24,11 @@ let menuLoaded = false;
// 加载导航菜单
async function loadNavMenu() {
if (menuLoaded) {
- console.log('[菜单模块] 菜单已加载,跳过');
+ // console.log('[菜单模块] 菜单已加载,跳过');
return;
}
- console.log('[菜单模块] loadNavMenu() 函数被调用');
+ // console.log('[菜单模块] loadNavMenu() 函数被调用');
const navMenu = document.getElementById('navMenu');
if (!navMenu) {
console.error('[菜单模块] 无法找到id为navMenu的元素,菜单加载失败');
@@ -36,18 +36,18 @@ async function loadNavMenu() {
}
try {
- console.log('[菜单模块] 正在从/api/config获取菜单配置...');
+ // console.log('[菜单模块] 正在从/api/config获取菜单配置...');
// 从API获取配置
const response = await fetch('/api/config');
- console.log('[菜单模块] API响应状态:', response.status, response.statusText);
+ // console.log('[菜单模块] API响应状态:', response.status, response.statusText);
if (!response.ok) {
throw new Error(`获取配置失败: ${response.status} ${response.statusText}`);
}
const config = await response.json();
- console.log('[菜单模块] 成功获取配置:', config);
+ // console.log('[菜单模块] 成功获取配置:', config);
// 确保menuItems存在且是数组
if (!config.menuItems || !Array.isArray(config.menuItems) || config.menuItems.length === 0) {
@@ -70,7 +70,7 @@ async function loadNavMenu() {
// 渲染导航菜单
function renderNavMenu(navMenuElement, menuItems) {
try {
- console.log('[菜单模块] 开始渲染导航菜单,菜单项数量:', menuItems.length);
+ // console.log('[菜单模块] 开始渲染导航菜单,菜单项数量:', menuItems.length);
// 清空现有内容
navMenuElement.innerHTML = '';
@@ -94,7 +94,7 @@ function renderNavMenu(navMenuElement, menuItems) {
// 添加菜单项
menuItems.forEach((item, index) => {
- console.log(`[菜单模块] 渲染菜单项 #${index+1}:`, item);
+ // console.log(`[菜单模块] 渲染菜单项 #${index+1}:`, item);
const menuItem = document.createElement('li');
menuItem.style.marginLeft = '25px'; // 增加间距
@@ -136,11 +136,11 @@ function renderNavMenu(navMenuElement, menuItems) {
// 绑定移动端菜单切换事件
menuToggle.addEventListener('click', () => {
- console.log('[菜单模块] 菜单切换按钮被点击');
+ // console.log('[菜单模块] 菜单切换按钮被点击');
navMenuElement.classList.toggle('active');
});
- console.log(`[菜单模块] 成功渲染了 ${menuItems.length} 个导航菜单项`);
+ // console.log(`[菜单模块] 成功渲染了 ${menuItems.length} 个导航菜单项`);
} catch (error) {
console.error('[菜单模块] 渲染导航菜单失败:', error);
navMenuElement.innerHTML = `菜单渲染失败: ${error.message}`;
@@ -149,6 +149,6 @@ function renderNavMenu(navMenuElement, menuItems) {
// 添加loadMenu函数,作为loadNavMenu的别名,确保与index.html中的调用匹配
function loadMenu() {
- console.log('[菜单模块] 调用loadMenu() - 转发到loadNavMenu()');
+ // console.log('[菜单模块] 调用loadMenu() - 转发到loadNavMenu()');
loadNavMenu();
}
\ No newline at end of file
diff --git a/hubcmdui/web/js/networkTest.js b/hubcmdui/web/js/networkTest.js
index 1be1779..d660676 100644
--- a/hubcmdui/web/js/networkTest.js
+++ b/hubcmdui/web/js/networkTest.js
@@ -4,16 +4,27 @@
const networkTest = {
// 初始化函数
init: function() {
- console.log('初始化网络测试模块...');
- this.initNetworkTest();
- return Promise.resolve();
+ // console.log('初始化网络测试模块...');
+ this.initNetworkTestControls(); // Renamed for clarity
+ this.displayInitialResultsMessage();
+ return Promise.resolve(); // Keep if other inits expect a Promise
},
- // 初始化网络测试界面
- initNetworkTest: function() {
+ displayInitialResultsMessage: function() {
+ const resultsDiv = document.getElementById('testResults');
+ if (resultsDiv) {
+ resultsDiv.innerHTML = '请选择参数并开始测试。
';
+ }
+ },
+
+ // 初始化网络测试界面控件和事件
+ initNetworkTestControls: function() {
const domainSelect = document.getElementById('domainSelect');
- const testType = document.getElementById('testType');
-
+ const testTypeSelect = document.getElementById('testType'); // Corrected ID reference
+ const startTestButton = document.getElementById('startTestBtn'); // Use ID
+ const clearResultsButton = document.getElementById('clearTestResultsBtn');
+ const resultsDiv = document.getElementById('testResults');
+
// 填充域名选择器
if (domainSelect) {
domainSelect.innerHTML = `
@@ -26,68 +37,128 @@ const networkTest = {
+
+
+
`;
+
+ // 添加选择变化事件,显示/隐藏自定义域名输入框
+ domainSelect.addEventListener('change', () => {
+ const customDomainContainer = document.getElementById('customDomainContainer');
+ if (customDomainContainer) {
+ customDomainContainer.style.display = domainSelect.value === 'custom' ? 'block' : 'none';
+ }
+ });
}
// 填充测试类型选择器
- if (testType) {
- testType.innerHTML = `
-
+ if (testTypeSelect) {
+ testTypeSelect.innerHTML = `
+
`;
}
- // 绑定测试按钮点击事件
- const testButton = document.querySelector('#network-test button');
- if (testButton) {
- testButton.addEventListener('click', this.runNetworkTest);
+ // 绑定开始测试按钮点击事件
+ if (startTestButton) {
+ // Bind the function correctly, preserving 'this' context if necessary,
+ // or ensure runNetworkTest doesn't rely on 'this' from the event handler.
+ // Using an arrow function or .bind(this) if runNetworkTest uses 'this.someOtherMethod'
+ startTestButton.addEventListener('click', () => this.runNetworkTest());
+ } else {
+ // console.error('未找到开始测试按钮 (ID: startTestBtn)');
+ }
+
+ // 绑定清空结果按钮点击事件
+ if (clearResultsButton && resultsDiv) {
+ clearResultsButton.addEventListener('click', () => {
+ resultsDiv.innerHTML = '结果已清空。
';
+ // Optionally, remove loading class if it was somehow stuck
+ resultsDiv.classList.remove('loading');
+ });
+ } else {
+ if (!clearResultsButton) { /* console.error('未找到清空结果按钮 (ID: clearTestResultsBtn)'); */ }
+ if (!resultsDiv) { /* console.error('未找到测试结果区域 (ID: testResults)'); */ }
}
},
// 运行网络测试
- runNetworkTest: function() {
- const domain = document.getElementById('domainSelect').value;
+ runNetworkTest: async function() { // Changed to async for await
+ let domain = document.getElementById('domainSelect').value;
const testType = document.getElementById('testType').value;
const resultsDiv = document.getElementById('testResults');
+ const startTestButton = document.getElementById('startTestBtn');
- // 验证选择了域名
- if (!domain) {
- core.showAlert('请选择目标域名', 'error');
+ // 处理自定义域名
+ if (domain === 'custom') {
+ const customDomain = document.getElementById('customDomain')?.value?.trim();
+ if (!customDomain) {
+ core.showAlert('请输入自定义域名进行测试。', 'warning');
+ return;
+ }
+ domain = customDomain;
+ } else if (!domain) {
+ core.showAlert('请选择目标域名进行测试。', 'warning');
return;
}
- resultsDiv.innerHTML = '测试中,请稍候...';
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 60000); // 60秒超时
+ if (!testType) {
+ core.showAlert('请选择测试类型。', 'warning');
+ return;
+ }
- fetch('/api/network-test', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ domain, type: testType }),
- signal: controller.signal
- })
- .then(response => {
+ resultsDiv.innerHTML = ''; // Clear previous content before adding loading class
+ resultsDiv.classList.add('loading');
+ if(startTestButton) startTestButton.disabled = true; // Disable button during test
+
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => {
+ controller.abort();
+ // logger.warn('Network test aborted due to timeout.');
+ }, 60000); // 60秒超时
+
+ try {
+ const response = await fetch('/api/network-test', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ // Add any necessary auth headers if your API requires them
+ // 'Authorization': 'Bearer ' + localStorage.getItem('authToken'),
+ },
+ body: JSON.stringify({ domain, type: testType }),
+ signal: controller.signal
+ });
+
clearTimeout(timeoutId);
+
if (!response.ok) {
- throw new Error('网络测试失败');
+ const errorText = await response.text();
+ let detail = errorText;
+ try {
+ const errorJson = JSON.parse(errorText);
+ detail = errorJson.message || errorJson.error || errorText;
+ } catch (e) { /* ignore parsing error, use raw text */ }
+ throw new Error(`网络连接正常,但测试执行失败 (状态: ${response.status}): ${detail}`);
}
- return response.text();
- })
- .then(result => {
- resultsDiv.textContent = result;
- })
- .catch(error => {
- console.error('网络测试出错:', error);
+ const result = await response.text();
+ // Format the plain text result in a tag for better display
+ resultsDiv.innerHTML = `${result}`;
+ } catch (error) {
+ clearTimeout(timeoutId); // Ensure timeout is cleared on any error
+ // console.error('网络测试出错:', error);
+ let errorMessage = '测试失败: ' + error.message;
if (error.name === 'AbortError') {
- resultsDiv.textContent = '测试超时,请稍后再试';
- } else {
- resultsDiv.textContent = '测试失败: ' + error.message;
+ errorMessage = '测试请求超时 (60秒)。请检查网络连接或目标主机状态。';
}
- });
+ resultsDiv.innerHTML = `${errorMessage}`;
+ } finally {
+ resultsDiv.classList.remove('loading');
+ if(startTestButton) startTestButton.disabled = false; // Re-enable button
+ }
}
};
-// 全局公开网络测试模块
+// 全局公开网络测试模块 (或者 integrate with app.js module system if you have one)
+// Ensure this is called after the DOM is ready, e.g., in app.js or a DOMContentLoaded listener
+// For now, let's assume app.js handles calling networkTest.init()
window.networkTest = networkTest;
diff --git a/hubcmdui/web/js/systemStatus.js b/hubcmdui/web/js/systemStatus.js
index ea2f9b8..abfe99c 100644
--- a/hubcmdui/web/js/systemStatus.js
+++ b/hubcmdui/web/js/systemStatus.js
@@ -10,41 +10,41 @@ let DEBUG_MODE = false; // 控制是否输出调试信息
const logger = {
debug: function(...args) {
if (DEBUG_MODE) {
- console.log('[systemStatus:DEBUG]', ...args);
+ // console.log('[systemStatus:DEBUG]', ...args);
}
},
log: function(...args) {
- console.log('[systemStatus]', ...args);
+ // console.log('[systemStatus]', ...args);
},
warn: function(...args) {
- console.warn('[systemStatus]', ...args);
+ // console.warn('[systemStatus]', ...args);
},
error: function(...args) {
- console.error('[systemStatus]', ...args);
+ // console.error('[systemStatus]', ...args);
},
// 开启或关闭调试模式
setDebug: function(enabled) {
DEBUG_MODE = !!enabled;
- console.log(`[systemStatus] 调试模式已${DEBUG_MODE ? '开启' : '关闭'}`);
+ // console.log(`[systemStatus] 调试模式已${DEBUG_MODE ? '开启' : '关闭'}`);
}
};
// 刷新系统状态
async function refreshSystemStatus() {
- logger.log('刷新系统状态...');
+ // logger.log('刷新系统状态...');
showSystemStatusLoading(); // 显示表格/活动列表的加载状态
showDashboardLoading(); // 显示仪表盘卡片的加载状态
try {
// 并行获取Docker状态和系统资源信息
- logger.log('获取Docker状态和系统资源信息');
+ // logger.log('获取Docker状态和系统资源信息');
const [dockerResponse, resourcesResponse] = await Promise.all([
- fetch('/api/docker/status').catch(err => { logger.error('Docker status fetch failed:', err); return null; }), // 添加 catch
- fetch('/api/system-resources').catch(err => { logger.error('System resources fetch failed:', err); return null; }) // 添加 catch
+ fetch('/api/docker/status').catch(err => { /* logger.error('Docker status fetch failed:', err); */ return null; }), // 添加 catch
+ fetch('/api/system-resources').catch(err => { /* logger.error('System resources fetch failed:', err); */ return null; }) // 添加 catch
]);
- logger.debug('API响应结果:', { dockerOk: dockerResponse?.ok, resourcesOk: resourcesResponse?.ok });
+ // logger.debug('API响应结果:', { dockerOk: dockerResponse?.ok, resourcesOk: resourcesResponse?.ok });
let dockerDataArray = null; // 用于存放容器数组
let isDockerServiceRunning = false; // 用于判断 Docker 服务本身是否响应
@@ -53,7 +53,7 @@ async function refreshSystemStatus() {
try {
// 假设 API 直接返回容器数组
dockerDataArray = await dockerResponse.json();
- logger.debug('Docker数据:', JSON.stringify(dockerDataArray));
+ // logger.debug('Docker数据:', JSON.stringify(dockerDataArray));
// 只有当返回的是数组,且状态属性表明 Docker 正在运行时,才认为 Docker 服务是运行的
if (Array.isArray(dockerDataArray)) {
@@ -69,19 +69,19 @@ async function refreshSystemStatus() {
// 只有在没有这两种特定错误时,才认为 Docker 服务正常
isDockerServiceRunning = !hasDockerUnavailableError && !hasContainerListError;
- logger.debug(`Docker服务状态: ${isDockerServiceRunning ? '运行中' : '未运行'}, 错误状态:`,
- { hasDockerUnavailableError, hasContainerListError });
+ // logger.debug(`Docker服务状态: ${isDockerServiceRunning ? '运行中' : '未运行'}, 错误状态:`,
+ // { hasDockerUnavailableError, hasContainerListError });
} else {
- logger.warn('Docker数据不是数组:', typeof dockerDataArray);
+ // logger.warn('Docker数据不是数组:', typeof dockerDataArray);
isDockerServiceRunning = false;
}
} catch (jsonError) {
- logger.error('解析Docker数据失败:', jsonError);
+ // logger.error('解析Docker数据失败:', jsonError);
dockerDataArray = []; // 解析失败视为空数组
isDockerServiceRunning = false; // JSON 解析失败,认为服务有问题
}
} else {
- logger.warn('获取Docker状态失败');
+ // logger.warn('获取Docker状态失败');
dockerDataArray = []; // 请求失败视为空数组
isDockerServiceRunning = false; // 请求失败,认为服务未运行
}
@@ -91,15 +91,15 @@ async function refreshSystemStatus() {
try {
// --- 添加日志:打印原始响应文本 ---
const resourcesText = await resourcesResponse.text();
- logger.debug('原始系统资源响应:', resourcesText);
+ // logger.debug('原始系统资源响应:', resourcesText);
resourcesData = JSON.parse(resourcesText); // 解析文本
- logger.debug('解析后的系统资源数据:', resourcesData);
+ // logger.debug('解析后的系统资源数据:', resourcesData);
} catch (jsonError) {
- logger.error('解析系统资源数据失败:', jsonError);
+ // logger.error('解析系统资源数据失败:', jsonError);
resourcesData = { cpu: null, memory: null, diskSpace: null };
}
} else {
- logger.warn(`获取系统资源失败, 状态: ${resourcesResponse?.status}`);
+ // logger.warn(`获取系统资源失败, 状态: ${resourcesResponse?.status}`);
resourcesData = { cpu: null, memory: null, diskSpace: null };
}
@@ -121,9 +121,9 @@ async function refreshSystemStatus() {
// --- 修改:将合并后的数据存入缓存 ---
currentSystemData = combinedData; // 缓存数据
- logger.debug('合并后的状态数据:', currentSystemData);
+ // logger.debug('合并后的状态数据:', currentSystemData);
updateSystemStatusUI(currentSystemData);
- logger.log('系统状态加载完成');
+ // logger.log('系统状态加载完成');
// 只在仪表盘页面(且登录框未显示时)才显示成功通知
const adminContainer = document.querySelector('.admin-container');
@@ -144,7 +144,7 @@ async function refreshSystemStatus() {
});
}
} catch (error) {
- logger.error('刷新系统状态出错:', error);
+ // logger.error('刷新系统状态出错:', error);
showSystemStatusError(error.message);
showDashboardError(error.message);
@@ -171,7 +171,7 @@ async function refreshSystemStatus() {
// 显示系统状态错误
function showSystemStatusError(message) {
- console.error('系统状态错误:', message);
+ // console.error('系统状态错误:', message);
// 更新Docker容器表格状态为错误
const containerBody = document.getElementById('dockerContainersBody');
@@ -250,16 +250,16 @@ function updateSystemStatusUI(data) {
try {
// 使用可选链和确保 parseDiskSpace 返回有效对象
const diskData = parseDiskSpace(data.diskSpace);
- console.log('[systemStatus] Parsed disk data for progress bar:', diskData);
+ // console.log('[systemStatus] Parsed disk data for progress bar:', diskData);
if (diskData && typeof diskData.usagePercent === 'number') {
updateProgressBar('diskSpaceProgress', diskData.usagePercent);
- console.log(`[systemStatus] Updated disk progress bar with: ${diskData.usagePercent}%`);
+ // console.log(`[systemStatus] Updated disk progress bar with: ${diskData.usagePercent}%`);
} else {
updateProgressBar('diskSpaceProgress', 0); // 出错或无数据时归零
- console.log('[systemStatus] Disk data invalid or missing usagePercent for progress bar.');
+ // console.log('[systemStatus] Disk data invalid or missing usagePercent for progress bar.');
}
} catch (e) {
- console.warn('[systemStatus] Failed to update disk progress bar:', e);
+ // console.warn('[systemStatus] Failed to update disk progress bar:', e);
updateProgressBar('diskSpaceProgress', 0); // 出错时归零
}
@@ -269,13 +269,13 @@ function updateSystemStatusUI(data) {
const memPercent = data.memory?.usedPercentage;
if (typeof memPercent === 'number') {
updateProgressBar('memoryProgress', memPercent);
- console.log(`[systemStatus] Updated memory progress bar with: ${memPercent}%`);
+ // console.log(`[systemStatus] Updated memory progress bar with: ${memPercent}%`);
} else {
updateProgressBar('memoryProgress', 0);
- console.log('[systemStatus] Memory data invalid or missing usedPercentage for progress bar.');
+ // console.log('[systemStatus] Memory data invalid or missing usedPercentage for progress bar.');
}
} catch (e) {
- console.warn('[systemStatus] Failed to update memory progress bar:', e);
+ // console.warn('[systemStatus] Failed to update memory progress bar:', e);
updateProgressBar('memoryProgress', 0);
}
@@ -285,22 +285,22 @@ function updateSystemStatusUI(data) {
const cpuUsage = data.cpu?.usage;
if (typeof cpuUsage === 'number') {
updateProgressBar('cpuProgress', cpuUsage);
- console.log(`[systemStatus] Updated CPU progress bar with: ${cpuUsage}%`);
+ // console.log(`[systemStatus] Updated CPU progress bar with: ${cpuUsage}%`);
} else {
updateProgressBar('cpuProgress', 0);
- console.log('[systemStatus] CPU data invalid or missing usage for progress bar.');
+ // console.log('[systemStatus] CPU data invalid or missing usage for progress bar.');
}
} catch (e) {
- console.warn('[systemStatus] Failed to update CPU progress bar:', e);
+ // console.warn('[systemStatus] Failed to update CPU progress bar:', e);
updateProgressBar('cpuProgress', 0);
}
- console.log('[systemStatus] UI update process finished.');
+ // console.log('[systemStatus] UI update process finished.');
}
// 显示系统状态加载
function showSystemStatusLoading() {
- console.log('显示系统状态加载中...');
+ // console.log('显示系统状态加载中...');
// 更新Docker容器表格状态为加载中
const containerTable = document.getElementById('dockerContainersTable');
@@ -348,7 +348,7 @@ function updateProgressBar(id, percentage) {
if (bar) {
// 确保 percentage 是有效的数字,否则设为 0
const validPercentage = (typeof percentage === 'number' && !isNaN(percentage)) ? Math.max(0, Math.min(100, percentage)) : 0;
- console.log(`[systemStatus] Setting progress bar ${id} to ${validPercentage}%`);
+ // console.log(`[systemStatus] Setting progress bar ${id} to ${validPercentage}%`);
bar.style.width = `${validPercentage}%`;
bar.setAttribute('aria-valuenow', validPercentage); // 更新 aria 属性
@@ -361,7 +361,7 @@ function updateProgressBar(id, percentage) {
bar.className = 'progress-bar bg-danger';
}
} else {
- console.warn(`[systemStatus] Progress bar element with id '${id}' not found.`);
+ // console.warn(`[systemStatus] Progress bar element with id '${id}' not found.`);
}
}
@@ -409,7 +409,7 @@ function showDetailsDialog(type) {
}
});
} catch (error) {
- console.error('显示详情对话框失败:', error);
+ // console.error('显示详情对话框失败:', error);
core.showAlert('加载详情时出错: ' + error.message, 'error');
}
}
@@ -545,7 +545,7 @@ function generateDiskDetailsHtml(diskData) {
// 更新Docker状态指示器
function updateDockerStatus(available) {
- console.log(`[systemStatus] Updating top Docker status indicator to: ${available ? 'running' : 'stopped'}`);
+ // console.log(`[systemStatus] Updating top Docker status indicator to: ${available ? 'running' : 'stopped'}`);
const statusIndicator = document.getElementById('dockerStatusIndicator'); // 假设这是顶部指示器的 ID
const statusText = document.getElementById('dockerStatusText'); // 假设这是顶部文本的 ID
@@ -668,26 +668,43 @@ function initDashboard() {
// 初始加载系统状态
refreshSystemStatus(); // <-- 调用确保加载数据
- console.log('仪表板初始化完成');
+ // console.log('仪表板初始化完成');
}
// 创建仪表板卡片
-function createDashboardCard(data) {
+function createDashboardCard(cardInfo) {
const card = document.createElement('div');
card.className = 'dashboard-card';
- card.id = `${data.id}-card`;
-
+ card.id = `${cardInfo.id}-card`;
+
+ // Icon: Use cardInfo.icon directly as it should contain the full class string (e.g., "fas fa-microchip")
+ const iconHtml = cardInfo.icon ? `` : '';
+
+ // Action: Determine if it's a link or a callback
+ let actionHtml = '';
+ if (cardInfo.actionLink) {
+ actionHtml = `${cardInfo.actionText || '查看'}`;
+ } else if (cardInfo.actionCallback && typeof cardInfo.actionCallback === 'function') {
+ // Register the callback and create a link to call handleCardAction
+ window.systemStatus.cardActionCallbacks[cardInfo.id] = cardInfo.actionCallback;
+ actionHtml = `${cardInfo.actionText || '操作'}`;
+ } else if (cardInfo.actionText) {
+ // Fallback if only actionText is provided (no specific action)
+ actionHtml = `${cardInfo.actionText}`;
+ }
+
card.innerHTML = `
-
-
+
+ ${iconHtml}
- ${data.title}
- ${data.value}
- ${data.description}
-
-
- ${data.action}
+
+ ${cardInfo.title || '未命名卡片'}
+
+ ${cardInfo.value !== undefined ? cardInfo.value : '--'} ${cardInfo.unit || ''}
+
+ ${cardInfo.description ? `${cardInfo.description}` : ''}
+ ${actionHtml ? `${actionHtml}` : ''}
`;
return card;
@@ -695,190 +712,175 @@ function createDashboardCard(data) {
// 更新仪表板卡片
function updateDashboardCards(data) {
- logger.debug('更新仪表板卡片:', data);
-
- if (!data) {
- logger.error('仪表板数据为空');
+ logger.debug('Updating dashboard cards with data:', data);
+
+ const dashboardGrid = document.querySelector('.dashboard-grid');
+ if (!dashboardGrid) {
+ logger.warn('Dashboard grid not found. Cannot update cards.');
return;
}
+
+ // 清空旧的回调
+ window.systemStatus.cardActionCallbacks = {};
+
+ // 确保有 dockerContainers 数据
+ const containers = Array.isArray(data.dockerContainers) ? data.dockerContainers : [];
- // 更新容器数量卡片
- const containersValue = document.getElementById('containers-value');
- if (containersValue) {
- // 确保 dockerContainers 是数组
- containersValue.textContent = Array.isArray(data?.dockerContainers) ? data.dockerContainers.length : '--';
- logger.debug(`容器数量卡片更新为: ${containersValue.textContent}`);
- }
-
- // 更新内存使用卡片
- const memoryValue = document.getElementById('memory-value');
- if (memoryValue) {
- let memPercent = null;
-
- logger.debug(`内存数据:`, data.memory);
-
- // 特别处理兼容层返回的数据格式
- if (data.memory && typeof data.memory === 'object') {
- // 首先检查是否有 percent 属性
- if (typeof data.memory.percent === 'string') {
- memPercent = parseFloat(data.memory.percent.replace('%', ''));
- }
- // 如果 percent 不存在或无效,尝试从 total 和 used 计算
- else if (typeof data.memory.total === 'number' && typeof data.memory.used === 'number' && data.memory.total > 0) {
- memPercent = (data.memory.used / data.memory.total) * 100;
- }
- // 将单位转换为更易读的格式
- const totalMemory = formatByteSize(data.memory.total);
- const usedMemory = formatByteSize(data.memory.used);
- const freeMemory = formatByteSize(data.memory.free);
-
- // 更新内存详情表格中的值
- updateMemoryDetailsTable(totalMemory, usedMemory, freeMemory, memPercent);
- }
-
- memoryValue.textContent = (typeof memPercent === 'number' && !isNaN(memPercent))
- ? `${memPercent.toFixed(1)}%` // 保留一位小数
- : '未知';
- logger.debug(`内存卡片更新为: ${memoryValue.textContent}`);
-
- // 更新内存进度条
- const memoryProgressBar = document.getElementById('memory-progress');
- if (memoryProgressBar && typeof memPercent === 'number' && !isNaN(memPercent)) {
- memoryProgressBar.style.width = `${memPercent}%`;
- if (memPercent > 90) {
- memoryProgressBar.className = 'progress-bar bg-danger';
- } else if (memPercent > 70) {
- memoryProgressBar.className = 'progress-bar bg-warning';
- } else {
- memoryProgressBar.className = 'progress-bar bg-success';
- }
- }
- }
-
- // 更新CPU负载卡片
- const cpuValue = document.getElementById('cpu-value');
- if (cpuValue) {
- let cpuUsage = null;
-
- // 特别处理兼容层返回的CPU数据
- if (data.cpu && typeof data.cpu === 'object') {
- logger.debug(`CPU数据:`, data.cpu);
-
- // 如果有loadAvg数组,使用第一个值(1分钟平均负载)
- if (Array.isArray(data.cpu.loadAvg) && data.cpu.loadAvg.length > 0) {
- // 负载需要除以核心数来计算百分比
- const cores = data.cpu.cores || 1;
- cpuUsage = (data.cpu.loadAvg[0] / cores) * 100;
- // 防止超过100%
- cpuUsage = Math.min(cpuUsage, 100);
-
- // 更新CPU详情表格
- updateCpuDetailsTable(data.cpu.cores, data.cpu.model, data.cpu.speed, cpuUsage);
- }
- }
-
- cpuValue.textContent = (typeof cpuUsage === 'number' && !isNaN(cpuUsage))
- ? `${cpuUsage.toFixed(1)}%` // 保留一位小数
- : '未知';
- logger.debug(`CPU卡片更新为: ${cpuValue.textContent}`);
-
- // 更新CPU进度条
- const cpuProgressBar = document.getElementById('cpu-progress');
- if (cpuProgressBar && typeof cpuUsage === 'number' && !isNaN(cpuUsage)) {
- cpuProgressBar.style.width = `${cpuUsage}%`;
- if (cpuUsage > 90) {
- cpuProgressBar.className = 'progress-bar bg-danger';
- } else if (cpuUsage > 70) {
- cpuProgressBar.className = 'progress-bar bg-warning';
- } else {
- cpuProgressBar.className = 'progress-bar bg-success';
- }
- }
- }
-
- // 更新磁盘空间卡片
- const diskValue = document.getElementById('disk-value');
- const diskTrend = document.getElementById('disk-trend');
- if (diskValue) {
- let diskPercent = null;
-
- if (data.disk && typeof data.disk === 'object') {
- logger.debug('磁盘数据:', data.disk);
-
- // 特别处理直接从df命令返回的格式
- if (data.disk.percent) {
- // 如果percent属性存在,直接使用
- if (typeof data.disk.percent === 'string') {
- diskPercent = parseFloat(data.disk.percent.replace('%', ''));
- } else if (typeof data.disk.percent === 'number') {
- diskPercent = data.disk.percent;
+ // 过滤掉错误指示对象和没有有效ID的容器
+ const validContainers = containers.filter(c => c && c.id && c.id !== 'n/a' && !c.error);
+ const runningContainersCount = validContainers.filter(c => c.state && typeof c.state === 'string' && c.state.toLowerCase().includes('running')).length;
+ const totalValidContainersCount = validContainers.length;
+
+ logger.debug('Valid containers for dashboard:', validContainers);
+ logger.debug(`Running: ${runningContainersCount}, Total Valid: ${totalValidContainersCount}`);
+
+ const cardsData = [
+ {
+ id: 'dockerStatus',
+ title: 'Docker 服务',
+ value: data.dockerStatus === 'running' ? '运行中' : '已停止',
+ description: data.dockerStatus === 'running' ? '服务连接正常' : '服务未连接或不可用',
+ icon: 'fab fa-docker',
+ color: data.dockerStatus === 'running' ? 'var(--success-color)' : 'var(--danger-color)',
+ actionText: data.dockerStatus === 'running' ? '查看容器' : null,
+ actionLink: data.dockerStatus === 'running' ? '#docker-status' : null,
+ valueClass: data.dockerStatus === 'running' ? 'text-success' : 'text-danger'
+ },
+ {
+ id: 'containersCount',
+ title: '运行中容器',
+ value: runningContainersCount,
+ description: `总容器数: ${totalValidContainersCount}`,
+ icon: 'fas fa-box-open',
+ color: 'var(--primary-color)',
+ actionText: '管理容器',
+ actionCallback: () => {
+ if (typeof dockerManager !== 'undefined' && typeof dockerManager.showContainersPage === 'function') {
+ core.navigateToSection('docker-status');
+ } else if (typeof dockerManager !== 'undefined' && typeof dockerManager.showManagementModal === 'function') {
+ dockerManager.showManagementModal();
+ } else {
+ window.location.hash = 'docker-status';
+ if (window.app && typeof window.app.handleNavigation === 'function') {
+ window.app.handleNavigation('docker-status');
+ } else if (core && typeof core.navigateToSection === 'function') {
+ core.navigateToSection('docker-status');
+ } else {
+ console.warn('No function found to display Docker management (modal or section).');
+ const sidebarItem = document.querySelector('li[data-section="docker-status"]');
+ if (sidebarItem) sidebarItem.click();
+ else alert('容器管理功能导航失败');
+ }
}
-
- // 更新磁盘详情表格
- updateDiskDetailsTable(
- data.disk.size || "未知",
- data.disk.used || "未知",
- data.disk.available || "未知",
- diskPercent
- );
- }
- } else if (data.diskSpace && typeof data.diskSpace === 'object') {
- logger.debug('使用旧磁盘数据格式:', data.diskSpace);
-
- // 兼容老的diskSpace字段
- if (data.diskSpace.percent) {
- if (typeof data.diskSpace.percent === 'string') {
- diskPercent = parseFloat(data.diskSpace.percent.replace('%', ''));
- } else if (typeof data.diskSpace.percent === 'number') {
- diskPercent = data.diskSpace.percent;
+ },
+ unit: '个'
+ },
+ {
+ id: 'cpuUsage',
+ title: 'CPU 使用率',
+ value: (() => {
+ if (data.cpu) {
+ if (typeof data.cpu.usage === 'number' && !isNaN(data.cpu.usage)) return data.cpu.usage.toFixed(1) + '%';
+ if (typeof data.cpu.currentLoad === 'number' && !isNaN(data.cpu.currentLoad)) return data.cpu.currentLoad.toFixed(1) + '%';
+ if (typeof data.cpu.load === 'number' && !isNaN(data.cpu.load)) return data.cpu.load.toFixed(1) + '%';
+ if (data.cpu.currentLoad && typeof data.cpu.currentLoad.avgload === 'number' && !isNaN(data.cpu.currentLoad.avgload)) return data.cpu.currentLoad.avgload.toFixed(1) + '%';
+ // Add logic to calculate from loadAvg, similar to generateCpuDetailsHtml
+ if (Array.isArray(data.cpu.loadAvg) && data.cpu.loadAvg.length > 0 && typeof data.cpu.loadAvg[0] === 'number' && !isNaN(data.cpu.loadAvg[0])) {
+ const cores = data.cpu.cores || 1;
+ const cpuUsageFromLoadAvg = (data.cpu.loadAvg[0] / cores) * 100;
+ return Math.min(cpuUsageFromLoadAvg, 100).toFixed(1) + '%';
+ }
}
-
- // 更新磁盘详情表格
- updateDiskDetailsTable(
- data.diskSpace.size || "未知",
- data.diskSpace.used || "未知",
- data.diskSpace.available || "未知",
- diskPercent
- );
+ return 'N/A';
+ })(),
+ description: `核心数: ${data.cpu ? (data.cpu.cores || 'N/A') : 'N/A'}`,
+ icon: 'fas fa-microchip',
+ color: 'var(--warning-color)',
+ actionText: '详细信息',
+ actionCallback: () => showDetailsDialog('cpu'),
+ unit: ''
+ },
+ {
+ id: 'memoryUsage',
+ title: '内存使用率',
+ value: (() => {
+ if (data.memory) {
+ // 优先基于 active 内存计算百分比 (更接近真实程序占用)
+ if (typeof data.memory.active === 'number' && typeof data.memory.total === 'number' && data.memory.total > 0 && !isNaN(data.memory.active)) {
+ return ((data.memory.active / data.memory.total) * 100).toFixed(1) + '%';
+ }
+ // 如果后端直接提供了基于某种标准计算的 usedPercentage,且 active 不可用,则使用它
+ if (typeof data.memory.usedPercentage === 'number' && !isNaN(data.memory.usedPercentage)) {
+ return data.memory.usedPercentage.toFixed(1) + '%';
+ }
+ // 最后,如果 active 和 usedPercentage 都没有,才基于 used 计算 (这可能包含缓存)
+ if (typeof data.memory.used === 'number' && typeof data.memory.total === 'number' && data.memory.total > 0 && !isNaN(data.memory.used)) {
+ return ((data.memory.used / data.memory.total) * 100).toFixed(1) + '%';
+ }
+ }
+ return 'N/A';
+ })(),
+ description: `已用: ${data.memory && typeof data.memory.used === 'number' && !isNaN(data.memory.used) ? formatByteSize(data.memory.used) : 'N/A'} / 总量: ${data.memory && data.memory.total ? formatByteSize(data.memory.total) : 'N/A'}`,
+ icon: 'fas fa-memory',
+ color: 'var(--info-color)',
+ actionText: '详细信息',
+ actionCallback: () => showDetailsDialog('memory'),
+ unit: ''
+ },
+ {
+ id: 'diskUsage',
+ title: '磁盘使用率',
+ value: data.disk && data.disk.percent ? data.disk.percent : 'N/A',
+ description: `可用: ${data.disk && data.disk.available ? data.disk.available : 'N/A'} / 总量: ${data.disk && data.disk.size ? data.disk.size : 'N/A'}`,
+ icon: 'fas fa-hdd',
+ color: 'var(--secondary-color)',
+ actionText: '详细信息',
+ actionCallback: () => showDetailsDialog('disk'),
+ unit: ''
+ },
+ {
+ id: 'uptime', // 假设有一个API可以获取系统启动时间
+ title: '系统运行时长',
+ value: data.uptime || 'N/A', // 从 data.uptime 获取
+ description: '系统持续运行时间',
+ icon: 'fas fa-clock',
+ color: 'var(--purple-color)', // 自定义颜色变量
+ actionText: '系统日志',
+ actionLink: '#system-logs', // 假设有系统日志页面
+ unit: ''
+ }
+ ];
+
+ dashboardGrid.innerHTML = ''; // 清空现有卡片
+
+ cardsData.forEach(cardInfo => {
+ if ((cardInfo.id === 'uptime' && !data.uptime) && cardInfo.id !== 'dockerStatus' && cardInfo.id !== 'containersCount') {
+ // 如果是 uptime卡片且没有uptime数据,则跳过 (但保留 Docker 和容器数量卡片)
+ if (cardInfo.id !== 'cpuUsage' && cardInfo.id !== 'memoryUsage' && cardInfo.id !== 'diskUsage') {
+ logger.debug(`Skipping card ${cardInfo.id} due to missing data or specific condition.`);
+ return;
+ }
+ }
+
+ // 特殊处理 Docker 服务卡片,即使服务未运行也显示
+ if (cardInfo.id === 'dockerStatus') {
+ // 总是创建 Docker 状态卡片
+ } else if (data.dockerStatus !== 'running' &&
+ (cardInfo.id === 'containersCount')) {
+ // 如果Docker服务未运行,则只显示 "N/A" 或 0 对于容器数量
+ cardInfo.value = 0; // 或 'N/A'
+ cardInfo.description = 'Docker 服务未运行';
+ } else if (data.dockerStatus !== 'running' && cardInfo.id !== 'uptime') {
+ // 如果 Docker 服务未运行,并且不是 uptime 卡片,则跳过其他依赖 Docker 的卡片
+ if (cardInfo.id !== 'cpuUsage' && cardInfo.id !== 'memoryUsage' && cardInfo.id !== 'diskUsage') {
+ logger.debug(`Skipping card ${cardInfo.id} because Docker is not running.`);
+ return;
}
}
- if (typeof diskPercent === 'number' && !isNaN(diskPercent)) {
- diskValue.textContent = `${diskPercent.toFixed(0)}%`; // 磁盘百分比通常不带小数
- logger.debug(`磁盘卡片更新为: ${diskValue.textContent}`);
-
- // 更新磁盘进度条
- const diskProgressBar = document.getElementById('disk-progress');
- if (diskProgressBar) {
- diskProgressBar.style.width = `${diskPercent}%`;
- if (diskPercent > 90) {
- diskProgressBar.className = 'progress-bar bg-danger';
- } else if (diskPercent > 70) {
- diskProgressBar.className = 'progress-bar bg-warning';
- } else {
- diskProgressBar.className = 'progress-bar bg-success';
- }
- }
-
- // 更新趋势信息
- if (diskTrend) {
- if (diskPercent > 90) {
- diskTrend.className = 'trend down text-danger';
- diskTrend.innerHTML = ' 磁盘空间不足';
- } else if (diskPercent > 75) {
- diskTrend.className = 'trend down text-warning';
- diskTrend.innerHTML = ' 磁盘使用率较高';
- } else {
- diskTrend.className = 'trend up text-success';
- diskTrend.innerHTML = ' 磁盘空间充足';
- }
- }
- } else {
- diskValue.textContent = '未知';
- if(diskTrend) diskTrend.innerHTML = '';
- logger.debug(`磁盘卡片值为未知,无效百分比: ${diskPercent}`);
- }
- }
+ const cardElement = createDashboardCard(cardInfo);
+ dashboardGrid.appendChild(cardElement);
+ });
}
// 格式化字节大小为易读格式
@@ -999,13 +1001,24 @@ function formatTime(timestamp) {
// 显示仪表盘加载状态
function showDashboardLoading() {
- const cards = ['containers', 'memory', 'cpu', 'disk'];
- cards.forEach(id => {
- const valueElement = document.getElementById(`${id}-value`);
- if (valueElement) {
- valueElement.innerHTML = '';
+ const dashboardGrid = document.querySelector('.dashboard-grid');
+ if (dashboardGrid) {
+ // Instead of targeting specific value elements, show a general loading message
+ // or a spinner within the grid itself if it's empty.
+ // For now, we'll rely on the card creation process to populate it.
+ // If cards are not yet defined, we can show a message.
+ if (dashboardGrid.children.length === 0) {
+ dashboardGrid.innerHTML = '正在加载控制面板数据...
';
}
- });
+ }
+ // The old method below targets specific value elements which might not exist before cards are drawn.
+ // const cards = ['dockerStatus', 'containersCount', 'cpuUsage', 'memoryUsage', 'diskUsage', 'uptime'];
+ // cards.forEach(id => {
+ // const valueElement = document.getElementById(`${id}-value`);
+ // if (valueElement) {
+ // valueElement.innerHTML = '';
+ // }
+ // });
}
// 显示仪表盘错误
@@ -1060,6 +1073,18 @@ window.systemStatus.initDashboard = initDashboard;
// 暴露调试设置函数,方便开发时打开调试
window.systemStatus.setDebug = logger.setDebug;
+// --- 新增:用于存储卡片操作回调 ---
+window.systemStatus.cardActionCallbacks = {};
+
+// --- 新增:处理卡片操作的通用函数 ---
+window.systemStatus.handleCardAction = function(cardId) {
+ if (window.systemStatus.cardActionCallbacks[cardId]) {
+ window.systemStatus.cardActionCallbacks[cardId]();
+ } else {
+ console.warn(`No action callback registered for card ID: ${cardId}`);
+ }
+};
+
/* 添加一些基础样式到 CSS (如果 web/style.css 不可用,这里会失败) */
/* 理想情况下,这些样式应该放在 web/style.css */
const customHelpStyles = `
diff --git a/hubcmdui/web/js/userCenter.js b/hubcmdui/web/js/userCenter.js
index 706c02e..1bd38eb 100644
--- a/hubcmdui/web/js/userCenter.js
+++ b/hubcmdui/web/js/userCenter.js
@@ -11,24 +11,24 @@ async function getUserInfo() {
if (!sessionData.authenticated) {
// 用户未登录,不显示错误,静默返回
- console.log('用户未登录或会话无效,跳过获取用户信息');
+ // console.log('用户未登录或会话无效,跳过获取用户信息');
return;
}
// 用户已登录,获取用户信息
- console.log('会话有效,尝试获取用户信息...');
+ // console.log('会话有效,尝试获取用户信息...');
const response = await fetch('/api/user-info');
if (!response.ok) {
// 检查是否是认证问题
if (response.status === 401) {
- console.log('会话已过期,需要重新登录');
+ // console.log('会话已过期,需要重新登录');
return;
}
throw new Error('获取用户信息失败');
}
const data = await response.json();
- console.log('获取到用户信息:', data);
+ // console.log('获取到用户信息:', data);
// 更新顶部导航栏的用户名
const currentUsername = document.getElementById('currentUsername');
@@ -58,7 +58,7 @@ async function getUserInfo() {
}
}
} catch (error) {
- console.error('获取用户信息失败:', error);
+ // console.error('获取用户信息失败:', error);
// 不显示错误通知,只在控制台记录错误
}
}
@@ -146,12 +146,12 @@ async function changePassword(event) {
}).then((result) => {
// 当计时器结束或弹窗被关闭时 (包括点击确定按钮)
if (result.dismiss === Swal.DismissReason.timer || result.isConfirmed) {
- console.log('计时器结束或手动确认,执行登出');
+ // console.log('计时器结束或手动确认,执行登出');
auth.logout();
}
});
} catch (error) {
- console.error('修改密码失败:', error);
+ // console.error('修改密码失败:', error);
core.showAlert('修改密码失败: ' + error.message, 'error');
}
}
@@ -219,7 +219,7 @@ function checkUcPasswordStrength() {
// 初始化用户中心
function initUserCenter() {
- console.log('初始化用户中心');
+ // console.log('初始化用户中心');
// 获取用户信息
getUserInfo();
@@ -241,7 +241,7 @@ function loadUserStats() {
// 导出模块
const userCenter = {
init: function() {
- console.log('初始化用户中心模块...');
+ // console.log('初始化用户中心模块...');
// 可以在这里调用初始化逻辑,也可以延迟到需要时调用
return Promise.resolve(); // 返回一个已解决的 Promise,保持与其他模块一致
},
diff --git a/hubcmdui/web/style.css b/hubcmdui/web/style.css
index bc0a6f7..e7e4138 100644
--- a/hubcmdui/web/style.css
+++ b/hubcmdui/web/style.css
@@ -531,27 +531,6 @@
transform: translateY(0); /* 覆盖默认的按钮hover效果 */
}
-.copy-success {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: rgba(0, 0, 0, 0.8);
- color: white;
- padding: 0.75rem 1.5rem;
- border-radius: var(--radius-md);
- z-index: 1000;
- animation: fadeInOut 1.5s ease-in-out;
- pointer-events: none;
-}
-
-@keyframes fadeInOut {
- 0% { opacity: 0; }
- 20% { opacity: 1; }
- 80% { opacity: 1; }
- 100% { opacity: 0; }
-}
-
/* 标签展示区域 - 修复宽度与主容器一致 */
#imageTagsView {
width: 100%;
@@ -1012,27 +991,6 @@
transform: translateY(0); /* 覆盖默认的按钮hover效果 */
}
-.copy-success {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: rgba(0, 0, 0, 0.8);
- color: white;
- padding: 0.75rem 1.5rem;
- border-radius: var(--radius-md);
- z-index: 1000;
- animation: fadeInOut 1.5s ease-in-out;
- pointer-events: none;
-}
-
-@keyframes fadeInOut {
- 0% { opacity: 0; }
- 20% { opacity: 1; }
- 80% { opacity: 1; }
- 100% { opacity: 0; }
-}
-
/* 标签展示区域 - 修复宽度与主容器一致 */
#imageTagsView {
width: 100%;
@@ -1840,17 +1798,32 @@
z-index: 1100;
animation: fadeIn 0.3s ease-in;
text-align: center;
- max-width: 80%;
+ max-width: 90%;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ }
+
+ .toast-notification i {
+ font-size: 1.2rem;
}
.toast-notification.error {
border-left: 4px solid var(--danger-color);
}
+ .toast-notification.error i {
+ color: var(--danger-color);
+ }
+
.toast-notification.info {
border-left: 4px solid var(--info-color);
}
+ .toast-notification.info i {
+ color: var(--info-color);
+ }
+
.toast-notification.fade-out {
opacity: 0;
transition: opacity 0.3s ease-out;
@@ -1997,38 +1970,56 @@
top: 20px;
left: 50%;
transform: translateX(-50%);
- background-color: var(--container-bg);
- color: var(--text-primary);
- padding: 1rem 2rem;
- border-radius: var(--radius-md);
- box-shadow: var(--shadow-lg);
+ /* Glassmorphism styles - Simplified & Reinforced */
+ background-color: rgba(250, 250, 250, 0.4); /* 调整了Alpha值,使其更透明一些 */
+ backdrop-filter: blur(12px); /* 调整模糊值 */
+ -webkit-backdrop-filter: blur(12px); /* Safari 兼容 */
+ border: 1px solid rgba(255, 255, 255, 0.2); /* 边框更柔和 */
+ color: #2A3749; /* 使用了您CSS变量中的 --text-primary */
+ padding: 1rem 1.5rem;
+ border-radius: 12px; /* 圆角调整 */
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1); /* 调整阴影 */
z-index: 1100;
- animation: fadeIn 0.3s ease-in;
+ animation: fadeIn 0.3s ease-in-out; /* 使用 ease-in-out */
text-align: center;
- max-width: 90%;
+ min-width: 250px; /* 最小宽度 */
+ max-width: 350px; /* 最大宽度,避免过宽 */
display: flex;
align-items: center;
- gap: 0.75rem;
+ gap: 0.8rem; /* 图标与文字间距 */
}
.toast-notification i {
- font-size: 1.2rem;
+ font-size: 1.25rem; /* 图标大小 */
+ color: inherit; /* 默认继承文本颜色 */
+}
+
+/* 状态特定样式 */
+.toast-notification.success {
+ /* background-color: rgba(230, 245, 230, 0.4); /* 可选:为成功状态叠加一层淡淡的绿色玻璃感 */
+ /* border-left: none !important; /* 强制移除任何可能的旧左边框 */
+}
+
+.toast-notification.success i {
+ color: var(--success-color); /* 使用CSS变量 */
}
.toast-notification.error {
- border-left: 4px solid var(--danger-color);
+ /* background-color: rgba(250, 230, 230, 0.4); /* 可选:为错误状态叠加一层淡淡的红色玻璃感 */
+ /* border-left: none !important; */
}
.toast-notification.error i {
- color: var(--danger-color);
+ color: var(--danger-color); /* 使用CSS变量 */
}
.toast-notification.info {
- border-left: 4px solid var(--info-color);
+ /* background-color: rgba(230, 240, 250, 0.4); /* 可选:为信息状态叠加一层淡淡的蓝色玻璃感 */
+ /* border-left: none !important; */
}
.toast-notification.info i {
- color: var(--info-color);
+ color: var(--info-color); /* 使用CSS变量 */
}
/* 优化标签表格性能 */
@@ -2104,50 +2095,6 @@
box-shadow: none;
}
-/* 更新toast通知样式 */
-.toast-notification {
- position: fixed;
- top: 20px;
- left: 50%;
- transform: translateX(-50%);
- background-color: var(--container-bg);
- color: var(--text-primary);
- padding: 1rem 2rem;
- border-radius: var(--radius-md);
- box-shadow: var(--shadow-lg);
- z-index: 1100;
- animation: fadeIn 0.3s ease-in;
- text-align: center;
- max-width: 90%;
- display: flex;
- align-items: center;
- gap: 0.75rem;
-}
-
-.toast-notification i {
- font-size: 1.2rem;
-}
-
-.toast-notification.error {
- border-left: 4px solid var(--danger-color);
-}
-
-.toast-notification.error i {
- color: var(--danger-color);
-}
-
-.toast-notification.info {
- border-left: 4px solid var(--info-color);
-}
-
-.toast-notification.info i {
- color: var(--info-color);
-}
-
-.toast-notification.fade-out {
- opacity: 0;
- transition: opacity 0.3s ease-out;
-}
/* 添加标签加载进度样式 */
.loading-progress {
@@ -2643,7 +2590,7 @@
/* 为 操作 列设置宽度 */
#dockerStatusTable th:nth-child(5),
#dockerStatusTable td:nth-child(5) {
- width: 150px; /* 或 20% */
+ width: 20%;
text-align: center; /* 操作按钮居中 */
}
@@ -2982,4 +2929,10 @@
#dockerStatusTable th:nth-child(5),
#dockerStatusTable td:nth-child(5) {
width: 20%;
+}
+
+/* 新增:使教程页面的代码块在悬停时也显示复制按钮 */
+#documentationText pre:hover .copy-btn,
+.doc-content pre:hover .copy-btn {
+ opacity: 1;
}
\ No newline at end of file