From 9014dc87f1037c3a3e01ce058a3ac7d8dd830efa Mon Sep 17 00:00:00 2001 From: BretRen Date: Sun, 20 Jul 2025 15:32:17 -0700 Subject: [PATCH] v1.0.0 --- .gitignore | 141 ++++++++++++++++++++ app.js | 324 ++++++++++++++++++++++++++++++++++++++++++++++ index.html | 107 +++++++++++++++ package-lock.json | 248 +++++++++++++++++++++++++++++++++++ package.json | 5 + server.js | 70 ++++++++++ style.css | 300 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1195 insertions(+) create mode 100644 .gitignore create mode 100644 app.js create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.js create mode 100644 style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24e96b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,141 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +.pdcc.login.token.txt + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite logs files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/app.js b/app.js new file mode 100644 index 0000000..9ada272 --- /dev/null +++ b/app.js @@ -0,0 +1,324 @@ +// 服务器管理相关代码 +const servers = JSON.parse(localStorage.getItem("servers")) || []; +let currentServer = null; +let currentSocket = null; + +// DOM 元素 +const serverList = document.getElementById("server-list"); +const noServerMessage = document.getElementById("no-server-message"); +const serverStatus = document.getElementById("server-status"); +const currentServerName = document.getElementById("current-server-name"); + +// 初始化UI +function initUI() { + updateServerList(); + if (servers.length > 0) { + noServerMessage.style.display = "none"; + serverStatus.style.display = "block"; + connectToServer(servers[0]); + } else { + noServerMessage.style.display = "block"; + serverStatus.style.display = "none"; + } +} + +// 更新服务器下拉列表 +function updateServerList() { + serverList.innerHTML = ""; + + if (servers.length === 0) { + const emptyItem = document.createElement("div"); + emptyItem.className = "dropdown-item"; + emptyItem.textContent = "无服务器"; + emptyItem.style.color = "#888"; + emptyItem.style.cursor = "default"; + serverList.appendChild(emptyItem); + return; + } + + servers.forEach((server, index) => { + const serverItem = document.createElement("div"); + serverItem.className = "dropdown-item"; + serverItem.innerHTML = ` +
${server.name}
+ ${server.address} +
+ +
+ `; + + serverItem.addEventListener("click", (e) => { + if (!e.target.classList.contains("delete-server")) { + connectToServer(server); + document.querySelector(".dropdown").classList.remove("open"); + } + }); + + // 添加删除按钮事件 + const deleteBtn = serverItem.querySelector(".delete-server"); + deleteBtn.addEventListener("click", (e) => { + e.stopPropagation(); + deleteServer(index); + }); + + serverList.appendChild(serverItem); + }); +} + +// 连接到指定服务器 +function connectToServer(server) { + // 断开现有连接 + if (currentSocket) { + currentSocket.disconnect(); + } + + currentServer = server; + currentServerName.textContent = server.name; + + // 显示连接状态 + document.getElementById("cpu-details").textContent = "连接中..."; + + // 建立新连接 + currentSocket = io(server.address, { + transports: ["websocket"], + reconnectionAttempts: 3, + timeout: 5000, + }); + + // 设置事件监听器 + setupSocketListeners(currentSocket); +} + +// 设置Socket.IO监听器 +function setupSocketListeners(socket) { + socket.on("connect", () => { + console.log("已连接到服务器:", currentServer.name); + }); + + socket.on("connect_error", (error) => { + console.error("连接错误:", error); + document.getElementById("cpu-details").textContent = "连接失败"; + }); + + socket.on("disconnect", (reason) => { + console.log("断开连接:", reason); + }); + + socket.on("status", (data) => { + updateMemory({ + totalmem: data.totalmem, + freemem: data.freemem, + }); + + updateCpuLoad(data.perCpuUsage); + updateLoadAvg(data.loadavg); + + if (data.uptime) { + updateUptime(data.uptime); + } + + const cpuCoreUsages = data.perCpuUsage || []; + updateCpuCores(cpuCoreUsages); + }); +} + +// 添加新服务器 +function addServer(name, address) { + // 验证地址格式 + if (!address.startsWith("http://") && !address.startsWith("https://")) { + address = "http://" + address; + } + + const newServer = { + name, + address: address.replace(/\/$/, ""), // 移除末尾的斜杠 + }; + + servers.push(newServer); + localStorage.setItem("servers", JSON.stringify(servers)); + + if (servers.length === 1) { + // 如果是第一个服务器,自动连接 + noServerMessage.style.display = "none"; + serverStatus.style.display = "block"; + connectToServer(newServer); + } + + updateServerList(); +} + +// 删除服务器 +function deleteServer(index) { + if (currentServer && servers[index].name === currentServer.name) { + if (currentSocket) { + currentSocket.disconnect(); + currentSocket = null; + } + + if (servers.length === 1) { + noServerMessage.style.display = "block"; + serverStatus.style.display = "none"; + } + } + + servers.splice(index, 1); + localStorage.setItem("servers", JSON.stringify(servers)); + updateServerList(); + + // 如果还有服务器,连接到第一个 + if (servers.length > 0 && !currentServer) { + connectToServer(servers[0]); + } +} + +// 初始化模态框 +const openModal = document.getElementById("openModal"); +const closeModal = document.getElementById("closeModal"); +const modal = document.getElementById("addServerModal"); +const addServerBtn = document.getElementById("add-server-btn"); + +openModal.addEventListener("click", () => { + modal.style.display = "flex"; + document.getElementById("server-name").focus(); +}); + +closeModal.addEventListener("click", () => { + modal.style.display = "none"; +}); + +// 点击背景区域关闭弹窗 +window.addEventListener("click", (e) => { + if (e.target === modal) { + modal.style.display = "none"; + } +}); + +// 添加服务器按钮事件 +addServerBtn.addEventListener("click", () => { + const name = document.getElementById("server-name").value.trim(); + const address = document.getElementById("server-address").value.trim(); + + if (name && address) { + addServer(name, address); + modal.style.display = "none"; + document.getElementById("server-name").value = ""; + document.getElementById("server-address").value = ""; + } else { + alert("请填写服务器名称和地址"); + } +}); + +// 回车键提交表单 +document.getElementById("server-address").addEventListener("keypress", (e) => { + if (e.key === "Enter") { + addServerBtn.click(); + } +}); + +// 下拉菜单切换 +const toggleBtn = document.querySelector(".dropdown-toggle"); +const dropdown = document.querySelector(".dropdown"); + +toggleBtn.addEventListener("click", () => { + dropdown.classList.toggle("open"); +}); + +// 点击外部关闭菜单 +document.addEventListener("click", (e) => { + if (!dropdown.contains(e.target)) { + dropdown.classList.remove("open"); + } +}); + +// 工具函数 +function bytesToGB(bytes) { + return (bytes / 1024 / 1024 / 1024).toFixed(2); +} + +function updateMemory(mem) { + const totalGB = bytesToGB(mem.totalmem); + const freeGB = bytesToGB(mem.freemem); + const usedGB = (totalGB - freeGB).toFixed(2); + const usedPercent = ((usedGB / totalGB) * 100).toFixed(1); + + document.getElementById("mem-used").textContent = `${usedPercent}%`; + document.getElementById("mem-progress").style.width = usedPercent + "%"; + document.getElementById( + "mem-details" + ).textContent = `${usedGB} GB / ${totalGB} GB`; +} + +function updateCpuLoad(perCpuUsage) { + if (!perCpuUsage || perCpuUsage.length === 0) { + document.getElementById("cpu-load").textContent = `N/A`; + document.getElementById("cpu-progress").style.width = "0%"; + document.getElementById("cpu-details").textContent = `无 CPU 使用数据`; + return; + } + const sum = perCpuUsage.reduce((a, b) => a + b, 0); + const avg = (sum / perCpuUsage.length).toFixed(1); + + document.getElementById("cpu-load").textContent = `${avg}%`; + document.getElementById("cpu-progress").style.width = avg + "%"; + document.getElementById("cpu-details").textContent = `每核CPU平均使用率`; +} + +function updateLoadAvg(loadavg) { + if (!loadavg || loadavg.length < 3) return; + + document.getElementById( + "loadavg-values" + ).textContent = `1min: ${loadavg[0].toFixed(2)}, 5min: ${loadavg[1].toFixed( + 2 + )}, 15min: ${loadavg[2].toFixed(2)}`; +} + +function updateUptime(uptime) { + if (!uptime) return; + + const days = Math.floor(uptime / 86400); + const hours = Math.floor((uptime % 86400) / 3600); + const minutes = Math.floor((uptime % 3600) / 60); + const seconds = Math.floor(uptime % 60); + + let uptimeStr = ""; + if (days > 0) uptimeStr += `${days}天 `; + if (hours > 0 || days > 0) uptimeStr += `${hours}小时 `; + if (minutes > 0 || hours > 0 || days > 0) uptimeStr += `${minutes}分 `; + uptimeStr += `${seconds}秒`; + + document.getElementById("uptime-value").textContent = uptimeStr; +} + +function updateCpuCores(usages) { + const container = document.getElementById("cpu-cores"); + container.innerHTML = ""; + + usages.forEach((percent, idx) => { + const coreDiv = document.createElement("div"); + coreDiv.className = "cpu-core"; + + // 创建进度条 + const progressBar = document.createElement("div"); + progressBar.className = "progress-bar"; + progressBar.style.height = "6px"; + progressBar.style.marginTop = "6px"; + progressBar.style.backgroundColor = "#2a2a2a"; + + const progress = document.createElement("div"); + progress.className = "progress"; + progress.style.height = "100%"; + progress.style.width = `${percent}%`; + progress.style.backgroundColor = + percent > 80 ? "#e91e63" : percent > 60 ? "#ff9800" : "#0bc"; + + progressBar.appendChild(progress); + + coreDiv.textContent = `核心 ${idx + 1}: ${percent}%`; + coreDiv.appendChild(progressBar); + container.appendChild(coreDiv); + }); +} + +// 初始化应用 +initUI(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..0098130 --- /dev/null +++ b/index.html @@ -0,0 +1,107 @@ + + + + + + 服务器状态面板 + + + + + +
+

服务器状态面板

+ +
+ +
+ +
+

无服务器

+

请先添加服务器

+
+ + + +
+ + +
+ + + + + +
+ + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f1e3763 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,248 @@ +{ + "name": "PDCC", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "socket.io": "^4.8.1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.0.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz", + "integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2a01bcc --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "socket.io": "^4.8.1" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..4d9451c --- /dev/null +++ b/server.js @@ -0,0 +1,70 @@ +const { Server } = require("socket.io"); +const http = require("http"); +const os = require("os"); + +const server = http.createServer(); +const io = new Server(server, { + cors: { + origin: "*", // 允许所有来源访问 + }, +}); + +function calculateCpuUsage(startCpus, endCpus) { + const usagePercentages = []; + + for (let i = 0; i < startCpus.length; i++) { + const start = startCpus[i].times; + const end = endCpus[i].times; + + const idleDiff = end.idle - start.idle; + const totalStart = Object.values(start).reduce((a, b) => a + b, 0); + const totalEnd = Object.values(end).reduce((a, b) => a + b, 0); + const totalDiff = totalEnd - totalStart; + + const usage = totalDiff === 0 ? 0 : (1 - idleDiff / totalDiff) * 100; + usagePercentages.push(+usage.toFixed(1)); + } + + return usagePercentages; +} + +function getSystemInfo(prevCpuInfo) { + const currentCpuInfo = os.cpus(); + const perCpuUsage = calculateCpuUsage(prevCpuInfo, currentCpuInfo); + + return { + totalmem: os.totalmem(), + freemem: os.freemem(), + loadavg: os.loadavg(), + uptime: os.uptime(), + perCpuUsage, + currentCpuInfo, + }; +} + +let prevCpuInfo = os.cpus(); + +io.on("connection", (socket) => { + console.log("客户端连接:", socket.id); + + // 先发一次状态信息 + const initInfo = getSystemInfo(prevCpuInfo); + prevCpuInfo = initInfo.currentCpuInfo; + socket.emit("status", initInfo); + + // 定时发送状态信息 + const interval = setInterval(() => { + const info = getSystemInfo(prevCpuInfo); + prevCpuInfo = info.currentCpuInfo; + socket.emit("status", info); + }, 3000); + + socket.on("disconnect", () => { + clearInterval(interval); + console.log("客户端断开:", socket.id); + }); +}); + +server.listen(3001, () => { + console.log("Socket.IO 服务运行在 http://localhost:3001"); +}); diff --git a/style.css b/style.css new file mode 100644 index 0000000..bb9d467 --- /dev/null +++ b/style.css @@ -0,0 +1,300 @@ +body { + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + background: #121212; + color: #ddd; + margin: 0; + padding: 20px; + min-height: 100vh; +} + +h1 { + margin-bottom: 20px; + color: #0bc; + font-weight: 600; + font-size: 1.8rem; +} + +.container { + max-width: 900px; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: 16px; +} + +.card-row { + display: flex; + gap: 16px; + flex-wrap: wrap; +} + +.card { + background: #1e1e1e; + border: 1px solid #0bc; + border-radius: 8px; + padding: 20px; + flex: 1; + min-width: 280px; + display: flex; + flex-direction: column; + box-sizing: border-box; + transition: border-color 0.3s ease; +} + +.card:hover { + border-color: #33d6ff; +} + +.card h2 { + margin: 0 0 12px 0; + color: #0bc; + font-weight: 600; + font-size: 1.25rem; +} + +.value { + font-size: 2rem; + font-weight: 700; + margin-bottom: 12px; + color: #e91e63; +} + +.progress-bar { + width: 100%; + height: 18px; + background: #2a2a2a; + border-radius: 8px; + overflow: hidden; + margin-bottom: 10px; +} + +.progress { + height: 100%; + background: #0bc; + width: 0; + transition: width 0.6s ease; +} + +.small-text { + font-size: 0.85rem; + color: #888; + margin-top: auto; +} + +.cpu-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); + gap: 10px; + margin-top: 10px; +} + +.cpu-core { + background: #2a2a2a; + border-radius: 6px; + padding: 12px 8px; + text-align: center; + font-weight: 600; + color: #0cf; + font-size: 0.9rem; +} + +.cpu-cores-card { + min-height: 180px; +} + +.no-server { + text-align: center; + padding: 3rem; + color: #888; + background: #1e1e1e; + border-radius: 8px; + border: 1px dashed #444; + margin-top: 2rem; +} + +.no-server h2 { + margin-bottom: 1rem; + color: #666; +} + +.floating-btn { + position: fixed; + bottom: 30px; + right: 30px; + width: 56px; + height: 56px; + background-color: transparent; + cursor: pointer; + z-index: 1000; + transition: transform 0.2s ease; +} + +.floating-btn:hover { + transform: scale(1.1); +} + +.modal { + display: none; + position: fixed; + z-index: 999; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + justify-content: center; + align-items: center; +} + +.modal-content { + background-color: #1e1e1e; + padding: 25px 30px; + border-radius: 10px; + width: 350px; + max-width: 90%; + box-shadow: 0 0 15px #00bcd4aa; + display: flex; + flex-direction: column; + gap: 15px; + position: relative; +} + +.modal-content h2 { + margin: 0 0 10px 0; + color: #00bcd4; + text-align: center; +} + +.modal-content input { + padding: 12px; + border: 1px solid #444; + background: #2a2a2a; + color: #fff; + border-radius: 5px; + font-size: 1rem; + width: 100%; + box-sizing: border-box; +} + +.submit-btn { + background-color: #00bcd4; + color: white; + border: none; + padding: 12px; + border-radius: 6px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.2s; + margin-top: 10px; +} + +.submit-btn:hover { + background-color: #00acc1; +} + +.close-modal { + position: absolute; + top: 15px; + right: 20px; + color: #aaa; + font-size: 24px; + font-weight: bold; + cursor: pointer; +} + +.close-modal:hover { + color: #fff; +} + +.header-bar { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 900px; + margin: 0 auto 20px; + padding: 0 10px; + flex-wrap: wrap; + gap: 15px; +} + +.dropdown { + position: relative; +} + +.dropdown-toggle { + background-color: #1e1e1e; + color: #00bcd4; + border: 1px solid #00bcd4; + padding: 10px 15px; + font-size: 1rem; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + min-width: 180px; + justify-content: space-between; +} + +.dropdown-toggle:hover { + background-color: #00bcd410; +} + +.dropdown-menu { + display: none; + position: absolute; + right: 0; + top: 110%; + background: #1e1e1e; + border: 1px solid #00bcd4; + border-radius: 6px; + padding: 8px 0; + z-index: 100; + min-width: 200px; + box-shadow: 0 0 10px #00bcd4aa; + max-height: 300px; + overflow-y: auto; +} + +.dropdown-item { + padding: 10px 15px; + color: #e0e0e0; + cursor: pointer; + transition: background-color 0.2s; +} + +.dropdown-item:hover { + background-color: #00bcd420; +} + +.dropdown.open .dropdown-menu { + display: block; +} + +/* 响应式调整 */ +@media (max-width: 768px) { + .header-bar { + flex-direction: column; + align-items: flex-start; + } + + .dropdown { + width: 100%; + } + + .dropdown-toggle { + width: 100%; + } + + .dropdown-menu { + width: 100%; + } + + .card { + min-width: 100%; + } +} + +.card { + margin: 8px; +} \ No newline at end of file