独立监控核心和模块

This commit is contained in:
zhongluofeng
2026-07-23 19:27:40 +08:00
parent 62db8e1982
commit e8267c244f
14 changed files with 2516 additions and 17 deletions
+256
View File
@@ -0,0 +1,256 @@
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { invoke } from '@tauri-apps/api/core'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { createLogger } from '@/lib/logger'
const logger = createLogger('monitor')
// ===== 与 Rust 端 / C# Contracts.cs 对应的数据结构(camelCase =====
// schemaVersion=1 契约由 Kernel 维护,前端按 schemaVersion 解析。
export interface SensorEntry {
id: string
name: string
/** 传感器类型(LHB SensorType 小写):temperature/load/power/voltage/fan/clock/data/smalldata/throughput/level/control 等 */
type: string
hardwareName: string
/** null 表示首轮未就绪或硬件不可读 */
value: number | null
unit: string
}
export interface SensorGroup {
id: string
name: string
sensors: SensorEntry[]
}
export interface SensorSnapshot {
schemaVersion: number
timestamp: number
/** 仅首个快照有意义,后续为 0 */
coldStartMs?: number
isAdmin: boolean
ready: boolean
groups: SensorGroup[]
}
export interface MonitorStatus {
running: boolean
pid: number | null
ready: boolean
sensorCount: number
restartCount: number
}
export interface MonitorKernelInfo {
path: string
exists: boolean
port: number
}
/** 连接状态机:与后端事件一一对应 */
export type ConnectionState = 'idle' | 'loading' | 'connected' | 'disconnected' | 'error'
/** 5 秒未收到 monitor-data 事件视为掉线(与后端心跳节奏一致) */
const STALE_TIMEOUT_MS = 5000
export const useMonitorStore = defineStore('monitor', () => {
// ===== state =====
const status = ref<MonitorStatus | null>(null)
const snapshot = ref<SensorSnapshot | null>(null)
const kernelInfo = ref<MonitorKernelInfo | null>(null)
const errorMsg = ref<string | null>(null)
/** 累计收到的 monitor-data 事件数,用于诊断与"已连接"判定 */
const eventCount = ref(0)
/** 最近一次收到 monitor-data 的时间戳(ms */
const lastEventTime = ref(0)
/** 是否正在启动 / 停止 Kernel(防止重复点击) */
const starting = ref(false)
const stopping = ref(false)
/** 是否已完成首次加载(避免初始 null/false 导致 UI 闪烁误导状态) */
const initialized = ref(false)
let unlistenFns: UnlistenFn[] = []
// ===== getters =====
/** 当前连接状态(基于 status + 最近事件时间推断) */
const connState = computed<ConnectionState>(() => {
if (errorMsg.value) return 'error'
if (!status.value) return 'idle'
if (!status.value.running) return 'idle'
if (!status.value.ready) return 'loading'
if (eventCount.value === 0) return 'loading'
if (Date.now() - lastEventTime.value > STALE_TIMEOUT_MS) return 'disconnected'
return 'connected'
})
/** 是否处于"已就绪 + 收到数据"的健康状态 */
const isLive = computed(() => connState.value === 'connected')
/** 按分组 id 查找快照 */
const groupById = computed(() => {
const map: Record<string, SensorGroup> = {}
for (const g of snapshot.value?.groups ?? []) map[g.id] = g
return map
})
/**
* 在指定分组下查找首个匹配的传感器值。
* @param groupId 分组 idcpu/memory/gpuintel/storage 等)
* @param matcher 传感器名匹配(精确或子串)
*/
function findSensorValue(groupId: string, matcher: { name?: string; hardwareName?: string; type?: string }): number | null {
const g = groupById.value[groupId]
if (!g) return null
const s = g.sensors.find(s =>
(!matcher.name || s.name === matcher.name || s.name.includes(matcher.name)) &&
(!matcher.hardwareName || s.hardwareName === matcher.hardwareName || s.hardwareName.includes(matcher.hardwareName)) &&
(!matcher.type || s.type === matcher.type)
)
return s?.value ?? null
}
// ===== actions =====
async function refreshStatus() {
try {
status.value = await invoke<MonitorStatus>('monitor_status')
errorMsg.value = null
} catch (e) {
logger.error('获取状态失败: ' + e)
} finally {
initialized.value = true
}
return status.value
}
async function refreshKernelInfo() {
try {
kernelInfo.value = await invoke<MonitorKernelInfo>('monitor_kernel_info')
} catch (e) {
logger.error('获取 Kernel 信息失败: ' + e)
}
return kernelInfo.value
}
async function start() {
if (starting.value) return
starting.value = true
errorMsg.value = null
try {
await invoke('monitor_start')
await refreshStatus()
} catch (e) {
errorMsg.value = String(e)
logger.error('启动失败: ' + e)
} finally {
starting.value = false
}
}
async function stop() {
if (stopping.value) return
stopping.value = true
try {
await invoke('monitor_stop')
snapshot.value = null
eventCount.value = 0
lastEventTime.value = 0
await refreshStatus()
} catch (e) {
errorMsg.value = String(e)
logger.error('停止失败: ' + e)
} finally {
stopping.value = false
}
}
/** 主动拉取一次性快照(切回 tab 时立即填充,不等下一个 SSE tick) */
async function fetchSnapshot() {
try {
snapshot.value = await invoke<SensorSnapshot>('monitor_get_snapshot')
} catch (e) {
logger.error('拉取快照失败: ' + e)
}
return snapshot.value
}
/** 订阅 Tauri 事件:monitor-data / monitor-ready / monitor-loading / monitor-disconnected / monitor-error */
async function subscribe() {
if (unlistenFns.length) return
unlistenFns.push(await listen<SensorSnapshot>('monitor-data', (e) => {
// schemaVersion 守卫:仅接受 v1,未来版本需在此处显式升级
if (e.payload?.schemaVersion !== 1) {
logger.warn('收到未知 schemaVersion: ' + e.payload?.schemaVersion)
return
}
snapshot.value = e.payload
eventCount.value++
lastEventTime.value = Date.now()
}))
unlistenFns.push(await listen('monitor-ready', () => {
refreshStatus()
}))
unlistenFns.push(await listen('monitor-loading', () => {
// 状态由 status 轮询反映
}))
unlistenFns.push(await listen('monitor-disconnected', () => {
logger.warn('SSE 断开,等待自动重连')
refreshStatus()
}))
unlistenFns.push(await listen<{ message?: string }>('monitor-error', (e) => {
errorMsg.value = e.payload?.message ?? 'Kernel 错误'
logger.error('Kernel 错误: ' + JSON.stringify(e.payload))
}))
}
function unsubscribe() {
unlistenFns.forEach(fn => fn())
unlistenFns = []
}
/** 模块挂载时调用:刷新状态 + 订阅事件 + 拉取一次快照 */
async function init() {
await Promise.all([refreshStatus(), refreshKernelInfo()])
await subscribe()
// 若 Kernel 已就绪,立即拉一次快照避免 UI 空白
if (status.value?.ready) {
await fetchSnapshot()
}
}
/** 模块卸载时调用:仅取消事件订阅,不停止 KernelKernel 由 ProcessManager 全局管理) */
function dispose() {
unsubscribe()
}
return {
// state
status,
snapshot,
kernelInfo,
errorMsg,
eventCount,
lastEventTime,
starting,
stopping,
initialized,
// getters
connState,
isLive,
groupById,
// actions
findSensorValue,
refreshStatus,
refreshKernelInfo,
start,
stop,
fetchSnapshot,
init,
dispose,
}
})