打造系统监控仪表盘
运行下面命令,可以实时可视化展示 cpu 占用、内存占用、进程列表等
npx gtop项目初始化与环境配置
需要创建一个新的项目目录并初始化 package.json 文件
mkdir cli-dashboard
cd cli-dashboard
npm init -y安装 Typescript
1. 安装 TypeScript 及类型定义:
npm install typescript @types/node --save-dev2. 生成 tsconfig.json 文件:
执行以下命令以创建 TypeScript 配置文件:
npx tsc --init要对 tsconfig.json 文件进行精简和优化,以满足项目需求:
{
"compilerOptions": {
"outDir": "dist",
"types": ["node"],
"target": "es2016",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}📘 说明:
"outDir": "dist": 指定 TypeScript 编译后输出的 JavaScript 文件目录"module": "NodeNext"和"moduleResolution": "NodeNext": 使 TypeScript 支持 Node.js 的 ECMAScript 模块(ESM)
3. 配置 package.json
为让 Node.js 正确解析 ES 模块,需要在 package.json 中添加 "type": "module":
{
"name": "cli-dashboard",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.12.12",
"typescript": "^5.4.5"
}
}安装核心依赖
我们的仪表盘需要 blessed 用于 UI 渲染,blessed-contrib 用于图表组件。
npm install blessed blessed-contrib --save
npm install @types/blessed --save-dev使用 blessed-contrib 构建仪表盘布局
blessed-contrib 提供一个强大的网格布局系统(Grid)
设计网格布局
将终端界面划分为一个 12x12 的网格。基于最终的实现,规划出各个组件的位置和尺寸:
📝 说明: 下方的布局描述与最终代码实现保持一致,但可能与示意图存在视觉差异。请以代码和文字描述为准
- CPU 使用率 (
contrib.line): 占据第 0-3 行, 0-5 列 (grid.set(0, 0, 4, 6, ...)) - 网络速率 (
contrib.line): 占据第 0-3 行, 6-11 列 (grid.set(0, 6, 4, 6, ...)) - 内存与交换分区 (
contrib.line): 占据第 4-7 行, 0-5 列 (grid.set(4, 0, 4, 6, ...)) - 内存使用率 (
contrib.donut): 占据第 4-5 行, 6-8 列 (grid.set(4, 6, 2, 3, ...)) - 交换分区使用率 (
contrib.donut): 占据第 4-5 行, 9-11 列 (grid.set(4, 9, 2, 3, ...)) - 磁盘使用情况 (
contrib.table): 占据第 6-8 行, 6-11 列 (grid.set(6, 6, 3, 6, ...)) - 进程列表 (
contrib.table): 占据第 8-11 行, 0-11 列 (`grid.set(8, 0, 4, 12, ...)
初始化组件
根据上述布局规划,在 src/index.ts 文件中编写代码,初始化各个组件
src/index.ts
import blessed from "blessed"
import contrib from "blessed-contrib"
// 创建屏幕实例
const screen = blessed.screen({
fullUnicode: true, // 支持全 Unicode 字符
smartCSR: true,
title: "CLI 系统监控仪表盘"
})
// 创建 12x12 网格布局
const grid = new contrib.grid({
rows: 12,
cols: 12,
screen: screen
})
// 初始化各个组件并放置在网格中
// grid.set(row, col, rowSpan, colSpan, component, options)
const cpuLineChart = grid.set(0, 0, 4, 6, contrib.line, {
label: "CPU 使用率 (%)",
showLegend: true,
legend: { width: 12 },
tags: true
})
const memLineChart = grid.set(4, 0, 4, 6, contrib.line, {
label: "内存与交换分区 (%) ",
showLegend: true,
legend: { width: 12 },
tags: true
})
const memDonut = grid.set(4, 6, 2, 3, contrib.donut, {
label: "内存使用率",
radius: 8,
arcWidth: 3,
remainColor: "black",
yPadding: 2
})
const swapDonut = grid.set(4, 9, 2, 3, contrib.donut, {
label: "交换分区使用率",
radius: 8,
arcWidth: 3,
remainColor: "black",
yPadding: 2
})
const netLineChart = grid.set(0, 6, 4, 6, contrib.line, {
label: "网络速率 (KB/s)",
showLegend: true,
legend: { width: 20 },
tags: true
})
const diskTable = grid.set(6, 6, 3, 6, contrib.table, {
label: "磁盘使用情况",
interactive: false,
columnWidth: [30, 10, 10, 10, 10, 30]
})
const processTable = grid.set(8, 0, 4, 12, contrib.table, {
label: "进程列表",
interactive: true,
columnWidth: [10, 30, 10, 10, 10, 30]
})
// 设置焦点
processTable.focus()
// 初始渲染
screen.render()
// 绑定退出快捷键 (q, Ctrl+C)
screen.key(["q", "C-c"], () => {
return process.exit(0)
})💡 提示:
grid.set方法的参数分别为(row, col, rowSpan, colSpan, component, options),用于指定组件在网格中的起始位置和占用的行列数。
3. 编译并预览布局
为了实时查看代码变更效果,可以使用 tsc -w 来监视文件变化并自动编译
# 终端一:启动 TypeScript 编译器监视模式
npx tsc -w
# 终端二:运行编译后的 JavaScript 文件
node dist/index.js执行后将看到一个静态的、尚未填充数据的仪表盘布局
systeminformation 获取系统信息
要构建一个系统监控仪表盘,核心是获取准确、实时的系统数据。为选用功能强大且广受欢迎的 systeminformation 库。systeminformation 能够提供涵盖硬件、操作系统、网络、服务等多个维度的详细信息
安装与基本使用
1. 安装 systeminformation
pnpm install systeminformation --save2. 创建测试文件
在项目根目录下创建 test.js 文件,用于探索 systeminformation 提供的各种 API
import si from "systeminformation"
// 1. 获取 CPU 负载
si.currentLoad((data) => {
console.log("CPU Load:", data)
})
// 2. 获取文件系统信息(磁盘分区)
si.fsSize((data) => {
console.log("File System:", data)
})
// 3. 获取内存信息
si.mem((data) => {
console.log("Memory:", data)
})
// 4. 获取网络接口和速度
si.networkInterfaceDefault((iface) => {
console.log("Default Network Interface:", iface)
si.networkStats(iface, (data) => {
console.log("Network Stats:", data)
})
})
// 5. 获取进程列表
si.processes((data) => {
// 数据量过大,建议使用调试器查看
// console.log('Processes:', data);
})3. 运行测试文件
node test.js调试与数据探索
si.processes() 返回的数据结构非常复杂且数据量庞大,直接使用 console.log 打印并不利于观察。使用 VS Code 的内置调试器是最高效的选择
1. 创建 launch.json
点击 VS Code 侧边栏的“运行和调试”图标,选择“创建 launch.json 文件”,并选择 “Node.js” 环境。这将自动在项目根目录下的 .vscode 文件夹中生成 launch.json 配置文件
2. 配置调试任务
修改 launch.json,创建专门用于调试 test.js 的任务:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug test.js",
"program": "${workspaceFolder}/test.js",
"console": "integratedTerminal"
}
]
}3. 设置断点并启动调试
在 test.js 文件中 si.processes() 的回调函数内设置一个断点,然后按下 F5 或点击调试面板的启动按钮。程序将在断点处暂停
通过调试器的“变量”窗口,可以从容地展开和检查 data 对象,清晰地看到每个进程的 PID、CPU 占用、内存使用等详细信息
功能模块实现
为代码的整洁与复用,为每个功能模块创建一个独立的类
CPU 监控
首先来实现 CPU 占用率的实时监控图表。
1. 创建 src/monitor/cpu.ts
创建一个 CpuMonitor 类,专门负责处理 CPU 数据的获取、格式化以及将其渲染到图表组件上。src/monitor/cpu.ts
import si from "systeminformation"
import contrib from "blessed-contrib"
// 为不同的 CPU 核心定义一组颜色
const colors = ["magenta", "cyan", "blue", "yellow", "green", "red"]
// 定义图表所需的数据结构类型
type CpuData = {
title: string
style: {
line: string
}
x: number[]
y: number[]
}
class CpuMonitor {
private lineChart: contrib.Widgets.LineElement
private cpuData: CpuData[] = []
private interval: NodeJS.Timeout | null = null
constructor(lineChart: contrib.Widgets.LineElement) {
this.lineChart = lineChart
}
init() {
si.currentLoad((data) => {
// 1. 初始化数据结构
this.cpuData = data.cpus.map((cpu, i) => ({
title: `CPU ${i + 1}`,
style: {
line: colors[i % colors.length]
},
x: Array(60)
.fill(0)
.map((_, i) => 60 - i), // X 轴刻度 (秒)
y: Array(60).fill(0) // Y 轴数据 (负载)
}))
// 2. 首次更新数据并渲染
this.updateData(data)
// 3. 设置定时器,每秒更新一次
this.interval = setInterval(() => {
si.currentLoad((data) => {
this.updateData(data)
})
}, 1000)
})
}
private updateData(data: si.Systeminformation.CurrentLoadData) {
data.cpus.forEach((cpu, i) => {
// 格式化负载百分比,保留一位小数并左侧补齐空格
let loadString = cpu.load.toFixed(1).toString().padStart(6, " ")
loadString = `${loadString}%`
// 更新图例标题,显示实时负载
this.cpuData[i].title = `CPU ${i + 1}${loadString}`
// 实现图表滚动效果
this.cpuData[i].y.shift() // 移除数组的第一个元素
this.cpuData[i].y.push(cpu.load) // 在数组末尾添加新数据
})
// 将更新后的数据设置到图表组件
this.lineChart.setData(this.cpuData)
// 重新渲染屏幕
this.lineChart.screen.render()
}
}
export default CpuMonitor📘 代码解析:
init()方法:
- 首次调用
si.currentLoad()获取 CPU 信息,并根据 CPU 核心数初始化cpuData数组。每个核心对应一个图表系列(一条线)x轴被初始化为一个从 60 到 1 的数组,代表最近 60 秒。y轴则全部填充为 0- 设置一个
setInterval定时器,每秒钟获取一次最新的 CPU 负载数据,并调用updateData方法updateData()方法:
- 遍历每个 CPU 核心的数据,更新其对应的图例标题以显示实时负载
- 核心技巧:通过
y.shift()和y.push(cpu.load),实现了数据的“先进先出”,从而在图表上营造出数据点从右向左滚动的视觉效果- 最后,调用
lineChart.setData()将新数据应用到图表,并调用screen.render()刷新终端界面
2. 在 index.ts 中使用 CpuMonitor
回到主文件,导入并实例化 CpuMonitor。src/index.ts (部分)
// ... (之前的代码)
import CpuMonitor from "./monitor/cpu"
// ... (组件初始化代码)
// 实例化并启动 CPU 监控
new CpuMonitor(cpuLineChart).init()
processTable.focus()
screen.render()
// ... (退出逻辑)内存监控
内存监控稍微复杂一些,因为它涉及三个组件:一个展示内存(Memory)和交换分区(Swap)历史占用率的折线图,以及两个分别显示它们当前占用百分比的环形图(Donut)。将把这三者的逻辑统一封装在 MemoryMonitor 类中
1. 创建 src/monitor/memory.ts
import si from "systeminformation"
import contrib from "blessed-contrib"
const colors = ["magenta", "cyan"]
type ChartType = contrib.Widgets.LineElement | contrib.Widgets.DonutElement
type MemData = {
title: string
style: {
line: string
}
x: number[]
y: number[]
}
class MemoryMonitor {
private lineChart: contrib.Widgets.LineElement
private memDonut: contrib.Widgets.DonutElement
private swapDonut: contrib.Widgets.DonutElement
private interval: NodeJS.Timeout | null = null
private memData: MemData[] = []
constructor(
lineChart: contrib.Widgets.LineElement,
memDonut: contrib.Widgets.DonutElement,
swapDonut: contrib.Widgets.DonutElement
) {
this.lineChart = lineChart
this.memDonut = memDonut
this.swapDonut = swapDonut
}
init() {
si.mem((data) => {
this.memData = [
{
title: "Memory",
style: { line: colors[0] },
x: Array(60)
.fill(0)
.map((_, i) => 60 - i),
y: Array(60).fill(0)
},
{
title: "Swap",
style: { line: colors[1] },
x: Array(60)
.fill(0)
.map((_, i) => 60 - i),
y: Array(60).fill(0)
}
]
this.updateData(data)
this.interval = setInterval(() => {
si.mem((data) => {
this.updateData(data)
})
}, 1000)
})
}
private updateData(data: si.Systeminformation.MemData) {
// 1. 计算内存和交换分区的占用率
let memPercent = +(100 * (1 - data.available / data.total)).toFixed()
let swapPercent = +(100 * (1 - data.swapfree / data.swaptotal)).toFixed()
swapPercent = isNaN(swapPercent) ? 0 : swapPercent
// 2. 更新折线图数据 (滚动效果)
this.memData[0].y.shift()
this.memData[0].y.push(memPercent)
this.memData[1].y.shift()
this.memData[1].y.push(swapPercent)
this.lineChart.setData(this.memData)
// 3. 更新两个环形图的数据
this.memDonut.setData([
{
percent: memPercent,
label: `${memPercent}%`,
color: colors[0]
}
])
this.swapDonut.setData([
{
percent: swapPercent,
label: `${swapPercent}%`,
color: colors[1]
}
])
// 4. 统一渲染屏幕
this.lineChart.screen.render()
}
}
export default MemoryMonitor📘 代码解析:
- 构造函数:接收并保存了
lineChart、memDonut和swapDonut三个组件的实例init()方法:与CpuMonitor类似,初始化了折线图的两条线(内存和交换分区)的数据结构,并设置了定时器updateData()方法:
- 通过
(1 - data.available / data.total)公式计算出内存的占用百分比。交换分区的计算方式同理isNaN(swapPercent) ? 0 : swapPercent用于处理swaptotal可能为 0 导致的NaN(Not a Number) 问题- 使用
shift()和push()更新折线图数据,实现滚动效果- 调用
setData()分别更新两个环形图的百分比和标签- 最后,只需调用一次
screen.render()即可将所有变更一次性刷新到终端上
2. 在 index.ts 中使用 MemoryMonitor
// ...
import MemoryMonitor from "./monitor/memory"
// ...
new CpuMonitor(cpuLineChart).init()
new MemoryMonitor(memLineChart, memDonut, swapDonut).init()
// ...网络监控
网络监控的核心是展示实时上传和下载的速率。与直接读取 CPU 或内存的百分比不同,速率需要通过计算“单位时间内的流量变化”来得出。将这个稍显复杂的逻辑封装在 NetworkMonitor 类中
1. 创建 src/monitor/network.ts
import si from "systeminformation"
import contrib from "blessed-contrib"
const colors = ["green", "red"]
type NetData = {
title: string
style: {
line: string
}
x: number[]
y: number[]
}
class NetworkMonitor {
private lineChart: contrib.Widgets.LineElement
private interval: NodeJS.Timeout | null = null
private netData: NetData[] = []
private lastCheckTime: number = 0
private lastRxBytes: number = 0
private lastTxBytes: number = 0
constructor(lineChart: contrib.Widgets.LineElement) {
this.lineChart = lineChart
}
init() {
si.networkStats().then((data) => {
this.netData = [
{
title: "Up",
style: { line: colors[0] },
x: Array(60)
.fill(0)
.map((_, i) => 60 - i),
y: Array(60).fill(0)
},
{
title: "Down",
style: { line: colors[1] },
x: Array(60)
.fill(0)
.map((_, i) => 60 - i),
y: Array(60).fill(0)
}
]
this.lastCheckTime = Date.now()
this.lastRxBytes = data[0].rx_bytes
this.lastTxBytes = data[0].tx_bytes
this.interval = setInterval(() => {
si.networkStats().then((data) => {
this.updateData(data[0])
})
}, 1000)
})
}
private updateData(data: si.Systeminformation.NetworkStatsData) {
const now = Date.now()
const timeDiff = (now - this.lastCheckTime) / 1000 // 秒
// 1. 计算上传和下载速率 (KB/s)
const rxSpeed = (data.rx_bytes - this.lastRxBytes) / timeDiff / 1024
const txSpeed = (data.tx_bytes - this.lastTxBytes) / timeDiff / 1024
// 2. 更新图例标题
this.netData[0].title = `Up: ${txSpeed.toFixed(2)} KB/s`
this.netData[1].title = `Down: ${rxSpeed.toFixed(2)} KB/s`
// 3. 更新图表数据 (滚动效果)
this.netData[0].y.shift()
this.netData[0].y.push(txSpeed)
this.netData[1].y.shift()
this.netData[1].y.push(rxSpeed)
this.lineChart.setData(this.netData)
// 4. 更新上一次记录的数据和时间
this.lastCheckTime = now
this.lastRxBytes = data.rx_bytes
this.lastTxBytes = data.tx_bytes
// 5. 渲染屏幕
this.lineChart.screen.render()
}
}
export default NetworkMonitor📘 代码解析:
init()方法:
- 首次调用
si.networkStats()获取初始的网络流量(总接收字节rx_bytes和总发送字节tx_bytes)。- 将初始流量和当前时间戳分别存入
lastRxBytes、lastTxBytes和lastCheckTime。- 设置定时器,每秒获取一次新的网络统计数据。
updateData()方法:
- 核心逻辑:计算当前时间与上一次记录时间的差值
timeDiff(单位:秒)。- 用当前的流量减去上一次记录的流量,得到时间间隔内的流量增量,再除以
timeDiff和 1024,即可得到单位为 KB/s 的速率。- 更新图表数据和图例标题,并刷新屏幕。
- 关键步骤:在方法末尾,必须用当前的数据更新
lastCheckTime、lastRxBytes和lastTxBytes,为下一次计算做准备。
2. 在 index.ts 中使用 NetworkMonitor
// ...
import NetworkMonitor from "./monitor/network"
// ...
new CpuMonitor(cpuLineChart).init()
new MemoryMonitor(memLineChart, memDonut, swapDonut).init()
new NetworkMonitor(netLineChart).init()
// ...磁盘监控
磁盘监控模块负责展示各个文件系统的使用情况。将使用 contrib.table 组件,以清晰的表格形式呈现每个分区的总空间、已用空间、可用空间和挂载点等信息
1. 创建 src/monitor/disk.ts
import si from "systeminformation"
import contrib from "blessed-contrib"
// 辅助函数:将字节转换为更易读的格式 (GB)
function bytesToGb(bytes: number): string {
return (bytes / 1024 / 1024 / 1024).toFixed(2) + " GB"
}
class DiskMonitor {
private table: contrib.Widgets.TableElement
private interval: NodeJS.Timeout | null = null
constructor(table: contrib.Widgets.TableElement) {
this.table = table
}
init() {
const updater = () => {
si.fsSize((data) => {
this.updateData(data)
})
}
updater()
// 磁盘信息通常不会频繁变动,所以设置一个较长的更新间隔,例如 1 小时
this.interval = setInterval(updater, 3600 * 1000)
}
private updateData(data: si.Systeminformation.FsSizeData[]) {
const tableData = data.map((disk) => [
disk.fs,
bytesToGb(disk.size),
bytesToGb(disk.used),
bytesToGb(disk.available),
`${disk.use.toFixed(2)}%`,
disk.mount
])
this.table.setData({
headers: ["Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on"],
data: tableData
})
this.table.screen.render()
}
}
export default DiskMonitor📘 代码解析:
bytesToGb():一个简单的辅助函数,用于将systeminformation返回的字节大小转换为以 GB 为单位的字符串,方便阅读init()方法:
- 与之前的模块不同,磁盘信息不需要每秒都更新。因此,设置了一个长达 1 小时的定时器 (
3600 * 1000毫秒) 来刷新数据。在init时会立即执行一次updater以便首次加载数据updateData()方法:
- 调用
si.fsSize()获取所有文件系统的大小信息- 使用
map方法遍历返回的磁盘数组,将其格式化为blessed-contrib表格组件所要求的二维数组格式- 调用
table.setData(),传入表头(headers)和格式化后的数据(data),更新表格内容
2. 在 index.ts 中使用 DiskMonitor
// ...
import DiskMonitor from "./monitor/disk"
// ...
new CpuMonitor(cpuLineChart).init()
new MemoryMonitor(memLineChart, memDonut, swapDonut).init()
new NetworkMonitor(netLineChart).init()
new DiskMonitor(diskTable).init()
// ...进程列表
进程列表是监控仪表盘中最复杂也最核心的模块之一。它需要实时获取系统中的所有进程,并根据 CPU 或内存占用进行排序,然后以表格形式动态展示。由于数据量大且更新频繁,性能是此模块需要重点关注的问题。
1. 创建 src/monitor/process.ts
import si from "systeminformation"
import contrib from "blessed-contrib"
class ProcessMonitor {
private table: contrib.Widgets.TableElement
private interval: NodeJS.Timeout | null = null
constructor(table: contrib.Widgets.TableElement) {
this.table = table
}
init() {
const updater = () => {
si.processes((data) => {
this.updateData(data)
})
}
updater()
this.interval = setInterval(updater, 2000) // 进程信息更新频率稍低,设置为 2 秒
}
private updateData(data: si.Systeminformation.ProcessesData) {
// 1. 按 CPU 使用率降序排序
const sortedProcesses = data.list.sort((a, b) => b.pcpu - a.pcpu)
// 2. 格式化数据以适应表格
const tableData = sortedProcesses.map((p) => [
p.pid.toString(),
p.name,
p.pcpu.toFixed(2),
p.pmem.toFixed(2),
p.state,
p.user
])
// 3. 设置表格数据
this.table.setData({
headers: ["PID", "Name", "CPU %", "Mem %", "State", "User"],
data: tableData
})
// 4. 渲染屏幕
this.table.screen.render()
}
}
export default ProcessMonitor📘 代码解析:
init()方法:设置了一个 2 秒的定时器来调用updater。因为进程信息量大且变化不如 CPU 负载剧烈,2 秒的刷新间隔是一个比较合理的选择,可以平衡实时性和性能开销updateData()方法:
- 核心逻辑:
data.list.sort((a, b) => b.pcpu - a.pcpu)是这里的关键。使用sort方法对systeminformation返回的进程列表进行排序,排序的依据是pcpu(CPU 使用百分比),b.pcpu - a.pcpu实现了降序排列- 将排序后的进程数据通过
map转换为表格需要的二维数组格式- 最后,将格式化后的数据更新到表格组件并刷新屏幕。
2. 在 index.ts 中使用 ProcessMonitor
// ...
import ProcessMonitor from "./monitor/process"
// ...
new CpuMonitor(cpuLineChart).init()
new MemoryMonitor(memLineChart, memDonut, swapDonut).init()
new NetworkMonitor(netSpark).init()
new DiskMonitor(diskDonut).init()
new ProcessMonitor(processTable).init()
// ...整合与启动
需要在主文件 src/index.ts 中导入所有监控类,并在初始化 UI 组件后,分别为它们创建实例并调用 init() 方法来启动监控。src/index.ts (最终代码)
import blessed from "blessed"
import contrib from "blessed-contrib"
import CpuMonitor from "./monitor/cpu"
import MemoryMonitor from "./monitor/memory"
import NetworkMonitor from "./monitor/network"
import DiskMonitor from "./monitor/disk"
import ProcessMonitor from "./monitor/process"
// 1. 初始化屏幕和网格
const screen = blessed.screen({
fullUnicode: true,
smartCSR: true,
title: "CLI 系统监控仪表盘"
})
const grid = new contrib.grid({
rows: 12,
cols: 12,
screen: screen
})
// 2. 实例化所有 UI 组件
const cpuLineChart = grid.set(0, 0, 4, 6, contrib.line, {
label: "CPU 使用率 (%)",
showLegend: true,
legend: { width: 12 },
tags: true
})
const memLineChart = grid.set(4, 0, 4, 6, contrib.line, {
label: "内存与交换分区 (%) ",
showLegend: true,
legend: { width: 12 },
tags: true
})
const memDonut = grid.set(4, 6, 2, 3, contrib.donut, {
label: "内存使用率",
radius: 8,
arcWidth: 3,
remainColor: "black",
yPadding: 2
})
const swapDonut = grid.set(4, 9, 2, 3, contrib.donut, {
label: "交换分区使用率",
radius: 8,
arcWidth: 3,
remainColor: "black",
yPadding: 2
})
const netLineChart = grid.set(0, 6, 4, 6, contrib.line, {
label: "网络速率 (KB/s)",
showLegend: true,
legend: { width: 20 },
tags: true
})
const diskTable = grid.set(6, 6, 3, 6, contrib.table, {
label: "磁盘使用情况",
interactive: false,
columnWidth: [30, 10, 10, 10, 10, 30]
})
const processTable = grid.set(8, 0, 4, 12, contrib.table, {
label: "进程列表",
interactive: true,
columnWidth: [10, 30, 10, 10, 10, 30]
})
// 3. 启动所有监控模块
new CpuMonitor(cpuLineChart).init()
new MemoryMonitor(memLineChart, memDonut, swapDonut).init()
new NetworkMonitor(netLineChart).init()
new DiskMonitor(diskTable).init()
new ProcessMonitor(processTable).init()
// 4. 设置退出快捷键
processTable.focus()
screen.key(["q", "C-c"], () => {
return process.exit(0)
})
screen.render()最后通过以下命令编译并运行你的仪表盘:
# 编译 TypeScript 代码
npm run build
# 运行应用
node dist/index.js现在,一个功能齐全、信息丰富的命令行系统监控仪表盘就呈现在你眼前了!
展望
尽管仪表盘已经相当出色,但它仍然是一个绝佳的起点,拥有广阔的扩展空间
- 增强交互性:
- 动态进程排序:当前进程列表仅按 CPU 使用率排序。你可以尝试监听键盘事件(如按 ‘m’ 键按内存排序,‘p’ 键按 PID 排序),让用户可以动态切换排序标准
- 进程管理功能:实现选中表格中的某个进程后,按特定键(如 ‘k’)可以结束该进程(需谨慎使用
process.kill(pid))
- 扩展监控维度:
- GPU 监控:如果设备拥有独立显卡,可以研究
systeminformation的graphics()方法,尝试添加 GPU 的温度和负载监控 - 传感器信息:利用
si.cpuTemperature()等方法,在仪表盘上增加一个显示主板或 CPU 实时温度的模块
- GPU 监控:如果设备拥有独立显卡,可以研究
- 提升用户体验:
- 主题与自定义:设计几套不同的颜色主题(
colors数组),允许用户通过按键或配置文件切换仪表盘的视觉风格 - 可配置布局:尝试让用户可以通过一个简单的 JSON 配置文件来调整组件的布局和显隐。
- 主题与自定义:设计几套不同的颜色主题(
- 代码与性能优化:
- 数据更新策略:对于进程列表等数据量大的模块,研究是否有更高效的数据更新策略(例如,仅更新变化的数据行),以减少不必要的重绘和计算开销,降低 CPU 占用
通过探索这些高级功能,将能更深入地理解 Node.js 在构建强大、高效的系统工具方面的巨大潜力