独立监控核心和模块

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
+586 -17
View File
@@ -1,24 +1,593 @@
<script setup lang="ts">
import { Activity } from '@lucide/vue'
import {
Activity, Play, Square, RefreshCw, Loader2, Cpu, MemoryStick,
Gauge, HardDrive, Settings as SettingsIcon, AlertTriangle,
ShieldCheck, ShieldAlert, Zap, Thermometer, Clock, ChevronDown,
} from '@lucide/vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { toast } from 'vue-sonner'
import { useMonitorStore, type SensorEntry, type SensorGroup, type ConnectionState } from '@/stores/monitorStore'
import { useModuleTabs } from '@/lib/useModuleTabs'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/ui/accordion'
import { Progress } from '@/components/ui/progress'
import { Separator } from '@/components/ui/separator'
const store = useMonitorStore()
// ===== Tab 配置(注册到 TitleBar 浮动切换器) =====
const activeTab = ref('overview')
const tabsListRef = useModuleTabs(activeTab, [
{ value: 'overview', label: '概览' },
{ value: 'details', label: '详细' },
{ value: 'settings', label: '设置' },
])
// ===== 关键指标历史 buffer(用于概览页 sparkline,不引入外部图表库) =====
// 容量 30 点,约对应 30 秒(fast 通道 1s 推送一次)
const HISTORY_LEN = 30
const cpuTempHistory = ref<number[]>([])
const cpuLoadHistory = ref<number[]>([])
const memLoadHistory = ref<number[]>([])
const gpuLoadHistory = ref<number[]>([])
function pushHistory(buf: typeof cpuTempHistory, v: number | null) {
if (v == null || !isFinite(v)) return
buf.value.push(v)
if (buf.value.length > HISTORY_LEN) buf.value.shift()
}
// 监听快照变化,提取关键指标入历史
watch(() => store.snapshot, (snap) => {
if (!snap) return
pushHistory(cpuTempHistory, store.findSensorValue('cpu', { name: 'CPU Package', type: 'temperature' }))
pushHistory(cpuLoadHistory, store.findSensorValue('cpu', { name: 'CPU Total', type: 'load' }))
// Memory 分组有两条同名 "Memory" 负载传感器(Virtual Memory + Total Memory),优先取 Total Memory
pushHistory(memLoadHistory, store.findSensorValue('memory', { name: 'Memory', hardwareName: 'Total Memory', type: 'load' }))
// Intel GPU 3D 负载传感器名为 "D3D 3D";部分 GPU 为 "GPU Core"
pushHistory(gpuLoadHistory,
store.findSensorValue('gpuintel', { name: 'D3D 3D', type: 'load' }) ??
store.findSensorValue('gpuintel', { name: 'GPU Core', type: 'load' }) ??
store.findSensorValue('gpuintel', { type: 'load' }),
)
}, { deep: false })
// ===== 关键指标 computed =====
const cpuTemp = computed(() => store.findSensorValue('cpu', { name: 'CPU Package', type: 'temperature' }))
const cpuLoad = computed(() => store.findSensorValue('cpu', { name: 'CPU Total', type: 'load' }))
const memLoad = computed(() => store.findSensorValue('memory', { name: 'Memory', hardwareName: 'Total Memory', type: 'load' }))
const gpuLoad = computed(() =>
store.findSensorValue('gpuintel', { name: 'D3D 3D', type: 'load' }) ??
store.findSensorValue('gpuintel', { name: 'GPU Core', type: 'load' }) ??
store.findSensorValue('gpuintel', { type: 'load' }),
)
const storageTemp = computed(() => store.findSensorValue('storage', { type: 'temperature' }))
// ===== 工具函数 =====
/** 格式化数值:整数型指标(负载/温度)保留 0 位,浮点型(电压/功率)保留 2 位 */
function fmt(v: number | null, digits = 1): string {
if (v == null || !isFinite(v)) return '--'
return v.toFixed(digits)
}
/** 温度颜色:绿(<50) → 黄(<70) → 橙(<85) → 红(>=85) */
function tempColor(t: number | null): string {
if (t == null) return 'text-muted-foreground'
if (t < 50) return 'text-emerald-500'
if (t < 70) return 'text-yellow-500'
if (t < 85) return 'text-orange-500'
return 'text-red-500'
}
/** 负载颜色:蓝(<50) → 紫(<80) → 红(>=80) */
function loadColor(v: number | null): string {
if (v == null) return 'text-muted-foreground'
if (v < 50) return 'text-sky-500'
if (v < 80) return 'text-violet-500'
return 'text-red-500'
}
/** 传感器类型 → 中文标签 */
const typeLabels: Record<string, string> = {
temperature: '温度',
load: '负载',
power: '功率',
voltage: '电压',
fan: '风扇',
clock: '时钟',
data: '容量',
smalldata: '容量',
throughput: '吞吐',
level: '等级',
control: '控制',
frequency: '频率',
factor: '因子',
timespan: '时长',
energy: '能量',
noise: '噪声',
conductivity: '电导率',
humidity: '湿度',
flow: '流量',
}
function typeLabel(t: string): string {
return typeLabels[t] ?? t
}
/** 分组 id → 显示名 + 图标组件 */
const groupMeta: Record<string, { name: string; icon: typeof Cpu }> = {
cpu: { name: 'CPU', icon: Cpu },
memory: { name: '内存', icon: MemoryStick },
gpuintel: { name: 'GPU', icon: Gauge },
gpuamd: { name: 'GPU', icon: Gauge },
gpunvidia: { name: 'GPU', icon: Gauge },
storage: { name: '存储', icon: HardDrive },
motherboard: { name: '主板', icon: Activity },
superio: { name: '超级 IO', icon: Activity },
embeddedcontroller: { name: '嵌入式控制器', icon: Activity },
battery: { name: '电池', icon: Activity },
network: { name: '网络', icon: Activity },
psu: { name: '电源', icon: Zap },
}
function groupDisplayName(id: string, fallback: string): string {
return groupMeta[id]?.name ?? fallback
}
function groupIcon(id: string): typeof Cpu {
return groupMeta[id]?.icon ?? Activity
}
// ===== 连接状态徽章 =====
const stateMeta: Record<ConnectionState, { text: string; class: string }> = {
idle: { text: '未启动', class: 'bg-muted text-muted-foreground' },
loading: { text: '启动中', class: 'bg-yellow-500/15 text-yellow-600 dark:text-yellow-400' },
connected: { text: '已连接', class: 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400' },
disconnected: { text: '已断开', class: 'bg-orange-500/15 text-orange-600 dark:text-orange-400' },
error: { text: '错误', class: 'bg-red-500/15 text-red-600 dark:text-red-400' },
}
// ===== sparkline 路径生成(无外部依赖) =====
function sparklinePath(data: number[], opts: { min?: number; max?: number; w?: number; h?: number } = {}): string {
if (data.length < 2) return ''
const w = opts.w ?? 100
const h = opts.h ?? 28
const min = opts.min ?? Math.min(...data)
const max = opts.max ?? Math.max(...data)
const range = max - min || 1
const step = w / (data.length - 1)
return data.map((v, i) => {
const x = i * step
const y = h - ((v - min) / range) * h
return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`
}).join(' ')
}
// ===== 分组列表(详细页用) =====
const groups = computed<SensorGroup[]>(() => store.snapshot?.groups ?? [])
/** 按 hardwareName 子分组,再按 type 二级分组(详细页用) */
function groupSensors(sensors: SensorEntry[]): { hardware: string; byType: { type: string; items: SensorEntry[] }[] }[] {
const byHw = new Map<string, SensorEntry[]>()
for (const s of sensors) {
if (!byHw.has(s.hardwareName)) byHw.set(s.hardwareName, [])
byHw.get(s.hardwareName)!.push(s)
}
return Array.from(byHw.entries()).map(([hw, items]) => {
const byType = new Map<string, SensorEntry[]>()
for (const s of items) {
if (!byType.has(s.type)) byType.set(s.type, [])
byType.get(s.type)!.push(s)
}
return {
hardware: hw,
byType: Array.from(byType.entries()).map(([type, list]) => ({ type, items: list })),
}
})
}
// ===== Accordion 折叠状态 =====
const accordionValue = ref<string[]>([])
// ===== 控制操作 =====
async function handleStart() {
await store.start()
if (store.connState === 'connected') toast.success('Kernel 已启动')
else if (store.errorMsg) toast.error('启动失败', { description: store.errorMsg })
}
async function handleStop() {
await store.stop()
toast.success('Kernel 已停止')
}
async function handleRefresh() {
await store.refreshStatus()
if (store.status?.ready) {
await store.fetchSnapshot()
toast.success('已刷新')
}
}
// 生命周期
onMounted(() => {
store.init()
})
onUnmounted(() => {
store.dispose()
})
// 当 Kernel 从未就绪变成就绪时,主动拉一次快照填充 UI
watch(() => store.status?.ready, (ready, prev) => {
if (ready && !prev) {
store.fetchSnapshot()
}
})
</script>
<template>
<div class="h-full p-6 overflow-y-auto">
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Activity class="h-5 w-5 text-primary" />
硬件监控模块
</CardTitle>
</CardHeader>
<CardContent>
<div class="flex flex-col items-center justify-center h-64 text-muted-foreground">
<Activity class="h-16 w-16 mb-4 opacity-50" />
<p>硬件监控功能开发中...</p>
<p class="text-sm mt-2">支持 CPUGPU内存硬盘等硬件数据监控</p>
<div class="h-full p-6">
<Tabs v-model="activeTab" class="h-full flex flex-col">
<div ref="tabsListRef">
<TabsList class="grid w-full max-w-md grid-cols-3 !bg-transparent !p-0 !shadow-none">
<TabsTrigger value="overview" class="gap-1.5"><Activity class="size-3.5" />概览</TabsTrigger>
<TabsTrigger value="details" class="gap-1.5"><Gauge class="size-3.5" />详细</TabsTrigger>
<TabsTrigger value="settings" class="gap-1.5"><SettingsIcon class="size-3.5" />设置</TabsTrigger>
</TabsList>
</div>
<!-- ===== 概览 ===== -->
<TabsContent value="overview" class="flex-1 mt-4 tab-animate">
<ScrollArea class="h-full pr-3">
<!-- 未启动占位 -->
<div v-if="!store.initialized" key="initializing" class="h-full flex flex-col items-center justify-center text-muted-foreground gap-2 pt-10">
<Loader2 class="size-8 animate-spin" />
<p class="text-sm">正在加载...</p>
</div>
<div v-else-if="store.connState === 'idle'" key="idle" class="h-full flex flex-col items-center justify-center text-muted-foreground gap-3 pt-10">
<Activity class="size-12 opacity-30" />
<p class="text-sm">Kernel 未启动</p>
<Button size="sm" :disabled="store.starting" @click="handleStart">
<Loader2 v-if="store.starting" class="size-3.5 animate-spin" />
<Play v-else class="size-3.5" />启动 Kernel
</Button>
</div>
<div v-else key="content" class="columns-1 md:columns-2 gap-4 [&>*]:mb-4 [&>*]:break-inside-avoid">
<!-- 状态卡片 -->
<Card>
<CardHeader class="pb-3">
<CardTitle class="flex items-center justify-between text-base">
<span class="flex items-center gap-2"><Activity class="size-4 text-primary" />Kernel 状态</span>
<span :class="['text-xs px-2 py-0.5 rounded-full', stateMeta[store.connState].class]">
{{ stateMeta[store.connState].text }}
</span>
</CardTitle>
</CardHeader>
<CardContent class="space-y-2 text-sm">
<div class="grid grid-cols-2 gap-x-4 gap-y-1.5">
<div class="flex justify-between">
<span class="text-muted-foreground">进程</span>
<span>{{ store.status?.running ? '运行中' : '已停止' }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">PID</span>
<span class="font-mono">{{ store.status?.pid ?? '--' }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">就绪</span>
<span>{{ store.status?.ready ? '是' : '否' }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">传感器数</span>
<span>{{ store.status?.sensorCount ?? '--' }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">重启次数</span>
<span>{{ store.status?.restartCount ?? 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">事件数</span>
<span>{{ store.eventCount }}</span>
</div>
</div>
<Separator />
<div class="flex items-center justify-between">
<span class="flex items-center gap-1.5 text-muted-foreground">
<component :is="store.snapshot?.isAdmin ? ShieldCheck : ShieldAlert" class="size-3.5" />
权限
</span>
<Badge :variant="store.snapshot?.isAdmin ? 'default' : 'outline'" :class="store.snapshot?.isAdmin ? 'bg-emerald-500 hover:bg-emerald-500' : ''">
{{ store.snapshot?.isAdmin ? '管理员' : '普通' }}
</Badge>
</div>
<div v-if="!store.snapshot?.isAdmin" class="text-xs text-orange-600 dark:text-orange-400 flex items-start gap-1.5 pt-1">
<AlertTriangle class="size-3.5 mt-0.5 shrink-0" />
<span>非管理员运行部分传感器电压/主板/SMBus可能不可读可在设置页查看降级说明</span>
</div>
<div class="flex items-center gap-2 pt-1">
<Button size="xs" variant="outline" :disabled="store.starting || store.stopping" @click="handleRefresh">
<RefreshCw class="size-3" />刷新
</Button>
<Button size="xs" variant="outline" :disabled="store.starting || store.stopping" @click="handleStop">
<Square class="size-3" />停止
</Button>
</div>
</CardContent>
</Card>
<!-- CPU 关键指标 -->
<Card>
<CardHeader class="pb-3">
<CardTitle class="flex items-center gap-2 text-base">
<Cpu class="size-4 text-primary" />CPU
</CardTitle>
</CardHeader>
<CardContent class="space-y-3">
<div class="flex items-end justify-between">
<div>
<div class="text-xs text-muted-foreground flex items-center gap-1"><Thermometer class="size-3" />封装温度</div>
<div :class="['text-2xl font-bold tabular-nums', tempColor(cpuTemp)]">
{{ fmt(cpuTemp, 0) }}<span class="text-sm font-normal">°C</span>
</div>
</div>
<svg v-if="cpuTempHistory.length >= 2" :width="100" :height="28" class="opacity-80">
<path :d="sparklinePath(cpuTempHistory)" :stroke="'currentColor'" :class="tempColor(cpuTemp)" fill="none" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<div>
<div class="flex items-center justify-between text-xs mb-1">
<span class="text-muted-foreground flex items-center gap-1"><Gauge class="size-3" />总负载</span>
<span :class="['font-medium tabular-nums', loadColor(cpuLoad)]">{{ fmt(cpuLoad, 0) }}%</span>
</div>
<Progress :model-value="cpuLoad ?? 0" class="h-1.5" />
</div>
</CardContent>
</Card>
<!-- 内存 -->
<Card>
<CardHeader class="pb-3">
<CardTitle class="flex items-center gap-2 text-base">
<MemoryStick class="size-4 text-primary" />内存
</CardTitle>
</CardHeader>
<CardContent class="space-y-3">
<div>
<div class="flex items-center justify-between text-xs mb-1">
<span class="text-muted-foreground">内存负载</span>
<span :class="['font-medium tabular-nums', loadColor(memLoad)]">{{ fmt(memLoad, 0) }}%</span>
</div>
<Progress :model-value="memLoad ?? 0" class="h-1.5" />
</div>
<svg v-if="memLoadHistory.length >= 2" :width="'100%'" :height="32" class="opacity-80" preserveAspectRatio="none" viewBox="0 0 100 32">
<path :d="sparklinePath(memLoadHistory, { min: 0, max: 100, w: 100, h: 32 })" stroke="currentColor" class="text-violet-500" fill="none" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</CardContent>
</Card>
<!-- GPU -->
<Card>
<CardHeader class="pb-3">
<CardTitle class="flex items-center gap-2 text-base">
<Gauge class="size-4 text-primary" />GPU
</CardTitle>
</CardHeader>
<CardContent class="space-y-3">
<div v-if="gpuLoad != null">
<div class="flex items-center justify-between text-xs mb-1">
<span class="text-muted-foreground">3D 负载</span>
<span :class="['font-medium tabular-nums', loadColor(gpuLoad)]">{{ fmt(gpuLoad, 0) }}%</span>
</div>
<Progress :model-value="gpuLoad ?? 0" class="h-1.5" />
</div>
<div v-else class="text-xs text-muted-foreground py-2 text-center">暂无 GPU 数据</div>
</CardContent>
</Card>
<!-- 存储 -->
<Card v-if="storageTemp != null">
<CardHeader class="pb-3">
<CardTitle class="flex items-center gap-2 text-base">
<HardDrive class="size-4 text-primary" />存储
</CardTitle>
</CardHeader>
<CardContent>
<div class="text-xs text-muted-foreground flex items-center gap-1"><Thermometer class="size-3" />温度</div>
<div :class="['text-2xl font-bold tabular-nums', tempColor(storageTemp)]">
{{ fmt(storageTemp, 0) }}<span class="text-sm font-normal">°C</span>
</div>
</CardContent>
</Card>
<!-- 断线提示 -->
<Card v-if="store.connState === 'disconnected'" class="border-orange-500/40">
<CardContent class="pt-4 flex items-start gap-2 text-sm">
<AlertTriangle class="size-4 text-orange-500 mt-0.5 shrink-0" />
<div>
<div class="font-medium text-orange-600 dark:text-orange-400">SSE 连接断开</div>
<div class="text-xs text-muted-foreground mt-0.5">Kernel 可能正在重启等待自动重连...</div>
</div>
</CardContent>
</Card>
<!-- 错误提示 -->
<Card v-if="store.errorMsg" class="border-red-500/40">
<CardContent class="pt-4 flex items-start gap-2 text-sm">
<AlertTriangle class="size-4 text-red-500 mt-0.5 shrink-0" />
<div>
<div class="font-medium text-red-600 dark:text-red-400">错误</div>
<div class="text-xs font-mono mt-0.5 break-all">{{ store.errorMsg }}</div>
</div>
</CardContent>
</Card>
</div>
</ScrollArea>
</TabsContent>
<!-- ===== 详细 ===== -->
<TabsContent value="details" class="flex-1 mt-4 tab-animate">
<div v-if="!store.snapshot" key="no-snapshot" class="h-full flex flex-col items-center justify-center text-muted-foreground gap-2 pt-10">
<Gauge class="size-12 opacity-30" />
<p class="text-sm">无快照数据</p>
<p v-if="store.connState === 'idle'" class="text-xs">请先在概览页启动 Kernel</p>
<Button v-else size="xs" variant="outline" :disabled="!store.status?.ready" @click="store.fetchSnapshot()">
<RefreshCw class="size-3" />拉取快照
</Button>
</div>
</CardContent>
</Card>
<ScrollArea v-else key="details-list" class="h-full pr-3">
<div class="flex items-center justify-between mb-3">
<p class="text-sm text-muted-foreground">{{ groups.length }} 个分组 / {{ store.status?.sensorCount ?? 0 }} 个传感器</p>
<Button size="xs" variant="outline" @click="store.fetchSnapshot()">
<RefreshCw class="size-3" />刷新
</Button>
</div>
<Accordion v-model="accordionValue" type="multiple" class="w-full space-y-2 pb-4">
<Card v-for="g in groups" :key="g.id" class="overflow-hidden py-0">
<AccordionItem :value="g.id" class="border-b-0">
<AccordionTrigger class="px-4 py-2.5 hover:no-underline">
<div class="flex items-center justify-between w-full pr-2">
<span class="flex items-center gap-2">
<component :is="groupIcon(g.id)" class="size-4 text-primary" />
<span class="font-medium">{{ groupDisplayName(g.id, g.name) }}</span>
<Badge variant="outline" class="font-normal">{{ g.sensors.length }}</Badge>
</span>
<span class="text-xs text-muted-foreground">{{ g.id }}</span>
</div>
</AccordionTrigger>
<AccordionContent class="px-4 pb-3 pt-0">
<div v-for="hwGroup in groupSensors(g.sensors)" :key="hwGroup.hardware" class="py-2 border-t first:border-t-0">
<div class="text-xs font-medium text-muted-foreground mb-1.5 flex items-center gap-1">
<ChevronDown class="size-3" />{{ hwGroup.hardware }}
</div>
<div v-for="typeGroup in hwGroup.byType" :key="typeGroup.type" class="mb-2">
<div class="text-[11px] uppercase tracking-wide text-muted-foreground/70 mb-1">{{ typeLabel(typeGroup.type) }}</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-0.5 text-xs">
<div v-for="s in typeGroup.items" :key="s.id" class="flex justify-between items-center py-0.5">
<span class="text-muted-foreground truncate pr-2" :title="s.name">{{ s.name }}</span>
<span class="font-mono tabular-nums shrink-0" :class="{ 'text-muted-foreground/50': s.value == null }">
{{ s.value == null ? 'N/A' : s.value.toFixed(s.type === 'voltage' || s.type === 'power' ? 2 : 1) }}
<span class="text-muted-foreground ml-0.5">{{ s.unit }}</span>
</span>
</div>
</div>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Card>
</Accordion>
</ScrollArea>
</TabsContent>
<!-- ===== 设置 ===== -->
<TabsContent value="settings" class="flex-1 mt-4 tab-animate">
<ScrollArea class="h-full pr-3">
<div class="max-w-2xl space-y-4 pb-4">
<!-- 控制 -->
<Card>
<CardHeader>
<CardTitle class="text-base flex items-center gap-2">
<Activity class="size-4 text-primary" />Kernel 控制
</CardTitle>
</CardHeader>
<CardContent class="space-y-3 text-sm">
<div class="flex items-center gap-2 flex-wrap">
<Button size="sm" :disabled="store.starting || store.stopping || store.connState !== 'idle'" @click="handleStart">
<Loader2 v-if="store.starting" class="size-3.5 animate-spin" />
<Play v-else class="size-3.5" />启动
</Button>
<Button size="sm" variant="destructive" :disabled="store.starting || store.stopping || store.connState === 'idle'" @click="handleStop">
<Loader2 v-if="store.stopping" class="size-3.5 animate-spin" />
<Square v-else class="size-3.5" />停止
</Button>
<Button size="sm" variant="outline" :disabled="!store.status?.ready" @click="handleRefresh">
<RefreshCw class="size-3.5" />刷新状态
</Button>
</div>
<div class="text-xs text-muted-foreground">
Kernel ProcessManager 统一管理应用启动时自动拉起崩溃自动重启
</div>
</CardContent>
</Card>
<!-- Kernel 信息 -->
<Card v-if="store.kernelInfo">
<CardHeader>
<CardTitle class="text-base flex items-center gap-2">
<SettingsIcon class="size-4 text-primary" />Kernel 信息
</CardTitle>
</CardHeader>
<CardContent class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-muted-foreground">端口</span>
<span class="font-mono">{{ store.kernelInfo.port }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">已安装</span>
<Badge :variant="store.kernelInfo.exists ? 'default' : 'destructive'" :class="store.kernelInfo.exists ? 'bg-emerald-500 hover:bg-emerald-500' : ''">
{{ store.kernelInfo.exists ? '是' : '否' }}
</Badge>
</div>
<div class="flex justify-between items-start gap-2">
<span class="text-muted-foreground shrink-0">路径</span>
<span class="font-mono text-xs text-right break-all" :title="store.kernelInfo.path">{{ store.kernelInfo.path }}</span>
</div>
</CardContent>
</Card>
<!-- 权限与降级说明 -->
<Card>
<CardHeader>
<CardTitle class="text-base flex items-center gap-2">
<ShieldCheck class="size-4 text-primary" />权限与降级
</CardTitle>
</CardHeader>
<CardContent class="space-y-2 text-sm text-muted-foreground">
<p>当前权限<Badge :variant="store.snapshot?.isAdmin ? 'default' : 'outline'" :class="store.snapshot?.isAdmin ? 'bg-emerald-500 hover:bg-emerald-500 ml-1' : 'ml-1'">{{ store.snapshot?.isAdmin ? '管理员' : '普通' }}</Badge></p>
<Separator />
<p class="text-xs leading-relaxed">
LibreHardwareMonitor 访问 SMBus部分 EC 传感器某些 GPU 传感器需要管理员权限
默认非提权运行覆盖大部分 CPU/GPU 温度通过 OHM RPC/WMI 仍可读牺牲部分主板/电压传感器
</p>
<p class="text-xs leading-relaxed text-orange-600 dark:text-orange-400 flex items-start gap-1.5">
<AlertTriangle class="size-3.5 mt-0.5 shrink-0" />
<span>提权策略任务计划程序免 UAC 启动将在阶段五实现当前以普通权限运行</span>
</p>
</CardContent>
</Card>
<!-- 关于 -->
<Card>
<CardHeader>
<CardTitle class="text-base flex items-center gap-2">
<Clock class="size-4 text-primary" />关于
</CardTitle>
</CardHeader>
<CardContent class="text-sm space-y-2 text-muted-foreground">
<p class="text-xs leading-relaxed">
数据源LibreHardwareMonitorLib 0.9.5Native AOT 编译独立进程
</p>
<p class="text-xs leading-relaxed">
通信HTTP + SSE本地 loopbackschemaVersion=1 数据契约
</p>
<p v-if="store.snapshot?.coldStartMs" class="text-xs leading-relaxed">
冷启动耗时{{ (store.snapshot.coldStartMs / 1000).toFixed(1) }}
</p>
</CardContent>
</Card>
</div>
</ScrollArea>
</TabsContent>
</Tabs>
</div>
</template>
</template>