Files
Havline/internal/sysinfo/sysinfo_windows.go
T
mrlee 8f6fac802f feat(web): 仪表盘补系统资源/中国 IP 段/通知通道与公网穿透卡,修复 frpc 版本读取
仪表盘:修「服务」卡写死的「正常」——改为按 Fonu/Nginx/DDNS/证书四项实际状态聚合,异常时点名并降级徽标

仪表盘:新增「性能」面板(今日异常/平均响应,/api/status 早已返回但从未展示)与「流量 Top 5」(复用已有逐规则流量轮询);/api/status 每 15 秒刷新并在页面隐藏时暂停(连同流量轮询)

仪表盘:新增「公网穿透」统计卡;运行状态面板增加公网反代行,数据来自新增的 GET /api/frpmulti/routes/summary(逐台服务端聚合,单台失败不中断)

仪表盘:新增一行三卡「系统资源 / 中国 IP 段 / 通知通道」;系统资源为新增跨平台采集 internal/sysinfo(Linux /proc、Windows kernel32,无新依赖)

FRP:修复 frpc 版本读不出来——RuntimeStatus.Version 带 omitempty,--version 探测失败时字段被省略,前端只能显示未检测;现回落按 <FrpDir>/bin/<版本>/ 目录名推导(与「FRP 应用配置」弹窗的 active_version 同源),并逐台服务端探测版本

清理:删除 DashboardView 死样式(.dashboard/.stat-highlight/.proxy-metric*)
2026-09-23 19:10:43 +08:00

94 lines
2.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build windows
package sysinfo
import (
"os"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetSystemTimes = kernel32.NewProc("GetSystemTimes")
procGlobalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx")
procGetDiskFreeSpaceExW = kernel32.NewProc("GetDiskFreeSpaceExW")
)
type memoryStatusEx struct {
Length uint32
MemoryLoad uint32
TotalPhys uint64
AvailPhys uint64
TotalPageFile uint64
AvailPageFile uint64
TotalVirtual uint64
AvailVirtual uint64
AvailExtendedVirtual uint64
}
func readPlatform() (Stats, CPUSample) {
var (
stats Stats
sample CPUSample
)
var idle, kernel, user syscall.Filetime
if ret, _, _ := procGetSystemTimes.Call(
uintptr(unsafe.Pointer(&idle)),
uintptr(unsafe.Pointer(&kernel)),
uintptr(unsafe.Pointer(&user)),
); ret != 0 {
// kernel 时间包含 idle,因此总时间 = kernel + user
sample = CPUSample{
Total: filetimeTicks(kernel) + filetimeTicks(user),
Idle: filetimeTicks(idle),
}
}
var mem memoryStatusEx
mem.Length = uint32(unsafe.Sizeof(mem))
if ret, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&mem))); ret != 0 {
stats.MemTotalBytes = mem.TotalPhys
stats.MemUsedBytes = mem.TotalPhys - mem.AvailPhys
}
stats.DiskTotalBytes, stats.DiskUsedBytes = diskUsage(volumeRoot())
return stats, sample
}
// volumeRoot 取当前工作目录所在盘符(Fonu 的数据目录与工作目录同盘),失败时回落到 C:\
func volumeRoot() string {
wd, err := os.Getwd()
if err != nil || len(wd) < 2 || wd[1] != ':' {
return `C:\`
}
return wd[:3]
}
func diskUsage(root string) (total, used uint64) {
rootPtr, err := syscall.UTF16PtrFromString(root)
if err != nil {
return 0, 0
}
var freeToCaller, totalBytes, totalFree uint64
ret, _, _ := procGetDiskFreeSpaceExW.Call(
uintptr(unsafe.Pointer(rootPtr)),
uintptr(unsafe.Pointer(&freeToCaller)),
uintptr(unsafe.Pointer(&totalBytes)),
uintptr(unsafe.Pointer(&totalFree)),
)
if ret == 0 || totalBytes == 0 {
return 0, 0
}
if totalFree > totalBytes {
return totalBytes, 0
}
return totalBytes, totalBytes - totalFree
}
// filetimeTicks 把 FILETIME(100ns 单位)折算为 10ms 刻度,避免累加时溢出
func filetimeTicks(ft syscall.Filetime) uint64 {
return (uint64(ft.HighDateTime)<<32 | uint64(ft.LowDateTime)) / 10000
}