diff --git a/src/class/Server.ts b/src/class/Server.ts index 1d5911d..a2c09f7 100644 --- a/src/class/Server.ts +++ b/src/class/Server.ts @@ -8,6 +8,8 @@ export default class Server { configClass: ConfigServer; console: ServerConsole; docker: Docker; + systemMonitor: SystemMonitor; + readonly serversStore = useServerConfigStore(); constructor(id: string) { @@ -15,6 +17,8 @@ export default class Server { this.configClass = new ConfigServer(this.serversStore.getServer(id)); this.console = new ServerConsole(this); this.docker = new Docker(this); + this.systemMonitor = new SystemMonitor(this); + } config(): ConfigServer { @@ -193,6 +197,7 @@ class Docker { try { const output = await this.server.console.execute("docker version"); const lines = output.split("\n"); + let foundCommunity = false; let version = null; @@ -240,10 +245,11 @@ class Docker { const command = `echo "===CONTAINERS===" && docker ps -a --format "{{.Status}}" && echo "===IMAGES===" && docker images --format "{{.Repository}}" && echo "===DANGLING===" && docker images -f dangling=true --format "{{.Repository}}" && echo "===SIZE===" && docker system df`; const output = await this.server.console.execute(command); - console.log("Docker command output:", output); // Debug log + console.log("Docker command output:", output); + + const lines = output.split('\n'); + let currentSection = ''; - const lines = output.split("\n"); - let currentSection = ""; const containers: string[] = []; const images: string[] = []; const dangling: string[] = []; @@ -277,8 +283,8 @@ class Docker { case "dangling": dangling.push(trimmedLine); break; - case "size": - if (trimmedLine.includes("Images")) { + case 'size': + if (trimmedLine.includes('Images')) { const sizeMatch = trimmedLine.match(/(\d+\.?\d*[GMK]?B)/); if (sizeMatch) { size = sizeMatch[1]; @@ -292,7 +298,7 @@ class Docker { let running = 0; let stopped = 0; for (const container of containers) { - if (container.includes("Up")) { + if (container.includes('Up')) { running++; } else { stopped++; @@ -303,19 +309,216 @@ class Docker { containers: { running, stopped, - total: running + stopped, + total: running + stopped }, images: { local: images.length, size, - dangling: dangling.length, - }, + dangling: dangling.length + } }; } catch (error) { console.error("Error retrieving Docker data:", error); return { containers: { running: 0, stopped: 0, total: 0 }, images: { local: 0, size: "0B", dangling: 0 }, + + }; + } + } +} + +class SystemMonitor { + readonly server: Server; + + constructor(server: Server) { + this.server = server; + } + + async getSystemInfo(): Promise<{ + hostname: string; + uptime: string; + os: string; + kernel: string; + architecture: string; + }> { + try { + const commands = [ + "hostname", + "uptime -p", + "uname -s", + "uname -r", + "uname -m", + ]; + + const results = await Promise.all( + commands.map((cmd) => this.server.console.execute(cmd)), + ); + + return { + hostname: results[0].trim(), + uptime: results[1].trim(), + os: results[2].trim(), + kernel: results[3].trim(), + architecture: results[4].trim(), + }; + } catch (error) { + console.error("Error getting system info:", error); + return { + hostname: "Unknown", + uptime: "Unknown", + os: "Unknown", + kernel: "Unknown", + architecture: "Unknown", + }; + } + } + + async getSystemStats(): Promise<{ + cpu: { + usage: number; + cores: number; + load: number[]; + }; + memory: { + total: number; + used: number; + free: number; + cached: number; + percentage: number; + }; + disk: { + total: number; + used: number; + free: number; + percentage: number; + }; + network: { + rx: number; + tx: number; + rxRate: number; + txRate: number; + }; + }> { + try { + const command = ` + echo "===CPU===" + # CPU usage from /proc/stat + head -1 /proc/stat | awk '{idle=\$5+\$6; total=\$2+\$3+\$4+\$5+\$6+\$7+\$8; print (1-idle/total)*100}' + nproc + uptime | awk -F'load average:' '{print \$2}' | awk '{print \$1","\$2","\$3}' | sed 's/,/ /g' + echo "===MEMORY===" + free -m | grep Mem | awk '{print \$2","\$3","\$4","\$6}' + echo "===DISK===" + df / | tail -1 | awk '{total=\$2/1024/1024; used=\$3/1024/1024; free=\$4/1024/1024; print total","used","free}' + echo "===NETWORK===" + # Get network stats from first active interface + cat /proc/net/dev | grep -E "(eth|ens|enp|wlan|wlp)" | head -1 | awk '{print \$2","\$10}' || echo "0,0" + `; + + const output = await this.server.console.execute(command); + console.log("System stats command output:", output); // Debug log + + const lines = output.split("\n"); + let currentSection = ""; + const cpuData: string[] = []; + const memoryData: string[] = []; + const diskData: string[] = []; + const networkData: string[] = []; + + for (const line of lines) { + const trimmedLine = line.trim(); + + if (trimmedLine === "===CPU===") { + currentSection = "cpu"; + continue; + } else if (trimmedLine === "===MEMORY===") { + currentSection = "memory"; + continue; + } else if (trimmedLine === "===DISK===") { + currentSection = "disk"; + continue; + } else if (trimmedLine === "===NETWORK===") { + currentSection = "network"; + continue; + } + + if (trimmedLine && !trimmedLine.includes("===")) { + switch (currentSection) { + case "cpu": + cpuData.push(trimmedLine); + break; + case "memory": + memoryData.push(trimmedLine); + break; + case "disk": + diskData.push(trimmedLine); + break; + case "network": + networkData.push(trimmedLine); + break; + } + } + } + + const cpuUsage = parseFloat(cpuData[0] || "0"); + const cores = parseInt(cpuData[1] || "1"); + const loadAvgStr = cpuData[2] || "0 0 0"; + const loadAvg = loadAvgStr + .split(" ") + .map(parseFloat) + .filter((n) => !isNaN(n)); + + const memoryStr = memoryData[0] || "0,0,0,0"; + const memoryValues = memoryStr.split(",").map(Number); + const [totalMB, usedMB, freeMB, cachedMB] = memoryValues; + const memoryPercentage = totalMB > 0 ? (usedMB / totalMB) * 100 : 0; + + const diskStr = diskData[0] || "0,0,0"; + const diskValues = diskStr.split(",").map(Number); + const [totalGB, usedGB, freeGB] = diskValues; + const diskPercentage = totalGB > 0 ? (usedGB / totalGB) * 100 : 0; + + const networkStr = networkData[0] || "0,0"; + const networkValues = networkStr.split(",").map((val) => { + const num = Number(val); + return isNaN(num) ? 0 : num; + }); + const [rxBytes, txBytes] = networkValues; + + return { + cpu: { + usage: cpuUsage, + cores: cores, + load: loadAvg.length >= 3 ? loadAvg : [0, 0, 0], + }, + memory: { + total: totalMB, + used: usedMB, + free: freeMB, + cached: cachedMB, + percentage: memoryPercentage, + }, + disk: { + total: totalGB, + used: usedGB, + free: freeGB, + percentage: diskPercentage, + }, + network: { + rx: rxBytes, + tx: txBytes, + rxRate: 0, + txRate: 0, + }, + }; + } catch (error) { + console.error("Error getting system stats:", error); + return { + cpu: { usage: 0, cores: 1, load: [0, 0, 0] }, + memory: { total: 0, used: 0, free: 0, cached: 0, percentage: 0 }, + disk: { total: 0, used: 0, free: 0, percentage: 0 }, + network: { rx: 0, tx: 0, rxRate: 0, txRate: 0 }, }; } } diff --git a/src/pages/server/index.vue b/src/pages/server/index.vue index 2151ec4..6a2983a 100644 --- a/src/pages/server/index.vue +++ b/src/pages/server/index.vue @@ -40,8 +40,8 @@ const tools = [ name: "System Monitor", desc: "Monitor system resources", icon: "mdi:chart-line", - disabled: true, - click: () => {}, + tag: "Beta", + click: () => router.push(`/server/${server.value?.id}/monitor`), }, { name: "Logs Viewer", diff --git a/src/pages/server/monitor.vue b/src/pages/server/monitor.vue new file mode 100644 index 0000000..1676aac --- /dev/null +++ b/src/pages/server/monitor.vue @@ -0,0 +1,495 @@ + + + diff --git a/src/router/index.ts b/src/router/index.ts index b88eee9..af0d79e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -46,6 +46,11 @@ const routes: RouteRecordRaw[] = [ name: "container-details", component: () => import("@/pages/server/docker/container/index.vue"), }, + { + path: "/server/:id/monitor", + name: "system-monitor", + component: () => import("@/pages/server/monitor.vue"), + }, { path: "/settings", name: "settings",