diff --git a/backend/src/main/java/com/sshmanager/controller/MonitorController.java b/backend/src/main/java/com/sshmanager/controller/MonitorController.java index 38b3028..ada3397 100644 --- a/backend/src/main/java/com/sshmanager/controller/MonitorController.java +++ b/backend/src/main/java/com/sshmanager/controller/MonitorController.java @@ -46,27 +46,68 @@ public class MonitorController { Map metrics = new HashMap<>(); - // 获取CPU使用率 + // 获取CPU使用率(兼容多种系统) try { - String cpuOutput = sshService.executeCommand(conn, password, privateKey, passphrase, - "top -bn1 | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' | awk '{print 100 - $1}'"); - double cpuUsage = Double.parseDouble(cpuOutput.trim()); + double cpuUsage = 0; + try { + // 优先使用top + String cpuOutput = sshService.executeCommand(conn, password, privateKey, passphrase, + "top -bn1 2>/dev/null | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' | awk '{print 100 - $1}'"); + if (!cpuOutput.trim().isEmpty()) { + cpuUsage = Double.parseDouble(cpuOutput.trim()); + } else { + throw new Exception("top command failed"); + } + } catch (Exception e) { + // 备用方案:使用/proc/stat计算(需要两次采样,这里简化处理用vmstat) + try { + String vmstatOutput = sshService.executeCommand(conn, password, privateKey, passphrase, + "vmstat 1 2 | tail -1 | awk '{print 100 - $15}'"); + cpuUsage = Double.parseDouble(vmstatOutput.trim()); + } catch (Exception e2) { + throw new Exception("Both top and vmstat failed"); + } + } metrics.put("cpuUsage", Math.round(cpuUsage * 10.0) / 10.0); } catch (Exception e) { metrics.put("cpuUsage", null); } - // 获取内存信息 + // 获取内存信息(兼容多种系统) try { - String memOutput = sshService.executeCommand(conn, password, privateKey, passphrase, - "free -b | grep Mem"); - String[] memParts = memOutput.trim().split("\\s+"); - long totalMem = Long.parseLong(memParts[1]); - long usedMem = Long.parseLong(memParts[2]); - double memUsage = (double) usedMem / totalMem * 100; - metrics.put("memTotal", totalMem); - metrics.put("memUsed", usedMem); - metrics.put("memUsage", Math.round(memUsage * 10.0) / 10.0); + long totalMem = 0; + long usedMem = 0; + try { + // 优先使用free -b + String memOutput = sshService.executeCommand(conn, password, privateKey, passphrase, + "free -b 2>/dev/null | grep Mem"); + if (!memOutput.trim().isEmpty()) { + String[] memParts = memOutput.trim().split("\\s+"); + if (memParts.length >= 3) { + totalMem = Long.parseLong(memParts[1]); + usedMem = Long.parseLong(memParts[2]); + } + } + } catch (Exception e) { + // 备用方案:从/proc/meminfo读取 + String meminfoOutput = sshService.executeCommand(conn, password, privateKey, passphrase, + "cat /proc/meminfo | grep -E 'MemTotal|MemAvailable'"); + String[] lines = meminfoOutput.trim().split("\n"); + if (lines.length >= 2) { + totalMem = Long.parseLong(lines[0].replaceAll("\\D+", "")) * 1024; + long availableMem = Long.parseLong(lines[1].replaceAll("\\D+", "")) * 1024; + usedMem = totalMem - availableMem; + } + } + + if (totalMem > 0 && usedMem >= 0) { + double memUsage = (double) usedMem / totalMem * 100; + metrics.put("memTotal", totalMem); + metrics.put("memUsed", usedMem); + metrics.put("memUsage", Math.round(memUsage * 10.0) / 10.0); + } else { + throw new Exception("Failed to parse memory info"); + } } catch (Exception e) { metrics.put("memTotal", null); metrics.put("memUsed", null); diff --git a/frontend/src/components/TerminalWidget.vue b/frontend/src/components/TerminalWidget.vue index 36c8343..d4515c2 100644 --- a/frontend/src/components/TerminalWidget.vue +++ b/frontend/src/components/TerminalWidget.vue @@ -3,7 +3,7 @@ import { nextTick, ref, onMounted, onUnmounted, watch } from 'vue' import { Terminal } from 'xterm' import { FitAddon } from '@xterm/addon-fit' import { useAuthStore } from '../stores/auth' -import axios from 'axios' +import client from '../api/client' import { Activity, Cpu, HardDrive, MemoryStick, Clock, ChevronUp, ChevronDown } from 'lucide-vue-next' import 'xterm/css/xterm.css' @@ -74,7 +74,7 @@ async function fetchMonitorData() { if (!props.active || status.value !== 'connected') return monitorLoading.value = true try { - const res = await axios.get(`/api/monitor/${props.connectionId}`) + const res = await client.get(`/monitor/${props.connectionId}`) monitorData.value = res.data } catch { // 静默失败,不影响终端使用 diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue index 4d54bb1..a1571b5 100644 --- a/frontend/src/layouts/MainLayout.vue +++ b/frontend/src/layouts/MainLayout.vue @@ -7,9 +7,9 @@ import { useSftpTabsStore } from '../stores/sftpTabs' import { useTerminalTabsStore } from '../stores/terminalTabs' import TerminalWorkspaceView from '../views/TerminalWorkspaceView.vue' import { ArrowLeftRight, Server, LogOut, Menu, X, Terminal, FolderOpen } from 'lucide-vue-next' - -const route = useRoute() -const router = useRouter() + +const route = useRoute() +const router = useRouter() const authStore = useAuthStore() const connectionsStore = useConnectionsStore() const sftpTabsStore = useSftpTabsStore() @@ -20,19 +20,19 @@ const terminalTabs = computed(() => tabsStore.tabs) const sftpTabs = computed(() => sftpTabsStore.tabs) const showTerminalWorkspace = computed(() => route.path === '/terminal') const keepTerminalWorkspaceMounted = computed(() => showTerminalWorkspace.value || terminalTabs.value.length > 0) - -connectionsStore.fetchConnections().catch(() => {}) - -function closeSidebar() { - sidebarOpen.value = false -} - -function handleTabClick(tabId: string) { - tabsStore.activate(tabId) - router.push('/terminal') - closeSidebar() -} - + +connectionsStore.fetchConnections().catch(() => {}) + +function closeSidebar() { + sidebarOpen.value = false +} + +function handleTabClick(tabId: string) { + tabsStore.activate(tabId) + router.push('/terminal') + closeSidebar() +} + function handleTabClose(tabId: string, event: Event) { event.stopPropagation() tabsStore.close(tabId) @@ -67,36 +67,36 @@ function handleSftpTabClose(tabId: string, connectionId: number, event: Event) { router.push('/connections') } - -