+
总负载
{{ fmt(cpuLoad, 0) }}%
@@ -352,64 +527,203 @@ watch(() => store.status?.ready, (ready, prev) => {
-
-
-
-
- 内存
+
+
+
+
+ GPU
+ {{ gpuModel ?? '--' }}
-
-
-
-
内存负载
-
{{ fmt(memLoad, 0) }}%
+
+
+
+
+
核心温度
+
+ {{ fmt(gpuTemp, 0) }}°C
+
-
+
+
功耗
+
{{ fmt(gpuPower, 1) }}W
+
+
-
-
-
-
-
-
-
-
- GPU
-
-
-
-
-
-
3D 负载
+
+
+
+ 3D 负载
{{ fmt(gpuLoad, 0) }}%
-
暂无 GPU 数据
-
-
-
-
+
+
+
+
+ 内存
+
+ {{ memTotalGB != null ? fmt(memTotalGB, 0) + ' GB' : '--' }}
+
+
+
+
+
+
+
+
已使用
+
+ {{ fmt(memUsedGB, 1) }} / {{ fmt(memTotalGB, 1) }} GB
+
+
+
+
+
+
+ 负载
+ {{ fmt(memLoad, 0) }}%
+
+
+
+
+
+
+
+
+
+
+ 网络
+
+
+
+
+
+
+
+
+
+ {{ downSpeed.value }}{{ downSpeed.unit }}
+
+
+
+
+
+
+
+
+
+ {{ upSpeed.value }}{{ upSpeed.unit }}
+
+
+
+
+
+
+
+
+
+
+
+
存储
-
- 温度
-
- {{ fmt(storageTemp, 0) }}
°C
+
+
+
+
+ {{ drive.name }}
+ {{ fmt(drive.temp, 0) }}°C
+
+
+
+ 使用率
+ {{ fmt(drive.usedPct, 0) }}%
+
+
+
+
+ 容量
+ {{ fmt(drive.usedGB, 1) }} / {{ fmt(drive.totalGB, 1) }} GB
+
+
+
+
+ 暂无存储数据
+
+
+
+
+
+
+
+ Kernel 状态
+
+
+ {{ stateMeta[store.connState].text }}
+
+
+ {{ store.snapshot?.isAdmin ? '管理员' : '普通' }}
+
+
+ 永久提权
+
+
+ 提权
+
+
+
+
+
+
+
PID: {{ store.status?.pid ?? '--' }}
+
传感器: {{ store.status?.sensorCount ?? '--' }}
+
重启: {{ store.status?.restartCount ?? 0 }}
+
事件: {{ store.eventCount }}
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
SSE 连接断开
@@ -419,8 +733,8 @@ watch(() => store.status?.ready, (ready, prev) => {
-
-
+
+
错误
@@ -513,6 +827,9 @@ watch(() => store.status?.ready, (ready, prev) => {
+
Kernel 由 ProcessManager 统一管理,应用启动时自动拉起,崩溃自动重启。
@@ -538,9 +855,38 @@ watch(() => store.status?.ready, (ready, prev) => {
{{ store.kernelInfo.exists ? '是' : '否' }}
-
+
路径
-
{{ store.kernelInfo.path }}
+
+
+
+ {{ pathDisplay || pathShort }}
+ {{ pathShort }}
+
+ {{ store.kernelInfo.path ?? '' }}
+
+
+
+
+
+
+ 复制路径
+
+
+
+
+
+ 在文件夹中显示
+
+
+
@@ -552,16 +898,48 @@ watch(() => store.status?.ready, (ready, prev) => {
权限与降级
-
- 当前权限:{{ store.snapshot?.isAdmin ? '管理员' : '普通' }}
+
+
+
当前权限
+
+
+ {{ store.snapshot?.isAdmin ? '管理员' : '普通' }}
+
+
+ 永久提权
+
+
+ 提权模式
+
+
+
- LibreHardwareMonitor 访问 SMBus、部分 EC 传感器、某些 GPU 传感器需要管理员权限。
- 默认非提权运行,覆盖大部分 CPU/GPU 温度(通过 OHM RPC/WMI 仍可读),牺牲部分主板/电压传感器。
+ LibreHardwareMonitor 访问 CPU MSR(温度/时钟)、存储 SMART、SMBus、EC 传感器需要管理员权限。
+ 普通权限下可读:CPU 负载/功率、内存、GPU 负载。不可读:CPU 温度/时钟、存储、主板/电压。
-
-
- 提权策略(任务计划程序免 UAC 启动)将在阶段五实现,当前以普通权限运行。
+
+
+
+
+
+ 提权:仅以管理员权限启动 ThingHK(弹 UAC)。ThingHK 崩溃不自动重启,停止走 /shutdown 接口。
+
+
+
+
+
+ 永久提权:以管理员权限重启 Thing 自身,ThingHK 子进程继承权限。标志持久化,后续每次启动自动触发 UAC;可在概览页点击"取消提权"关闭。
+
+
+
+
+
+ 永久提权模式:Thing 与 ThingHK 均以管理员权限运行,ProcessManager 可直接 kill,崩溃自动重启可用。
+
+
+
+ 提权模式运行中:仅 ThingHK 管理员权限,进程不归 ProcessManager 管控,崩溃不自动重启;停止通过 /shutdown 接口优雅退出。
@@ -590,4 +968,76 @@ watch(() => store.status?.ready, (ready, prev) => {
+
+
+
diff --git a/src/stores/monitorStore.ts b/src/stores/monitorStore.ts
index 8eb3587..b790dec 100644
--- a/src/stores/monitorStore.ts
+++ b/src/stores/monitorStore.ts
@@ -42,6 +42,10 @@ export interface MonitorStatus {
ready: boolean
sensorCount: number
restartCount: number
+ /** 是否为提权模式(通过 UAC 以管理员权限启动) */
+ elevated: boolean
+ /** Thing 自身是否以管理员权限运行(永久提权模式) */
+ thingElevated: boolean
}
export interface MonitorKernelInfo {
@@ -50,6 +54,48 @@ export interface MonitorKernelInfo {
port: number
}
+// ===== 硬件监控配置(与 ThingHK HardwareConfig.cs 对应) =====
+
+export interface HardwareTypeInfo {
+ key: string
+ name: string
+ enabled: boolean
+ requiresAdmin: boolean
+}
+
+export interface SensorTypeInfo {
+ key: string
+ name: string
+ enabled: boolean
+}
+
+export interface HardwareConfig {
+ hardware: Record
+ sensorTypes: Record
+}
+
+export interface HardwareConfigResponse {
+ config: HardwareConfig
+ availableHardware: HardwareTypeInfo[]
+ availableSensorTypes: SensorTypeInfo[]
+}
+
+export interface HardwareConfigUpdateResponse {
+ success: boolean
+ restartRequired: boolean
+ configPath: string | null
+}
+
+/** 网速数据(由 Tauri network_monitor 模块推送,独立于 ThingHK Kernel) */
+export interface NetworkSpeed {
+ /** 下载速率(bytes/s) */
+ downloadBps: number
+ /** 上传速率(bytes/s) */
+ uploadBps: number
+ /** 时间戳(ms) */
+ timestamp: number
+}
+
/** 连接状态机:与后端事件一一对应 */
export type ConnectionState = 'idle' | 'loading' | 'connected' | 'disconnected' | 'error'
@@ -70,9 +116,18 @@ export const useMonitorStore = defineStore('monitor', () => {
const starting = ref(false)
const stopping = ref(false)
+ /** 网速数据(由 network_monitor 后台任务推送,独立于 Kernel) */
+ const networkSpeed = ref(null)
+
/** 是否已完成首次加载(避免初始 null/false 导致 UI 闪烁误导状态) */
const initialized = ref(false)
+ /** 永久提权标志是否已启用(后续启动自动触发 UAC) */
+ const elevateOnLaunch = ref(false)
+
+ /** 硬件监控配置(可用硬件 + 传感器类型清单 + 当前启用状态) */
+ const hardwareConfig = ref(null)
+
let unlistenFns: UnlistenFn[] = []
// ===== getters =====
@@ -122,8 +177,6 @@ export const useMonitorStore = defineStore('monitor', () => {
errorMsg.value = null
} catch (e) {
logger.error('获取状态失败: ' + e)
- } finally {
- initialized.value = true
}
return status.value
}
@@ -169,6 +222,96 @@ export const useMonitorStore = defineStore('monitor', () => {
}
}
+ /** 以管理员权限重启 Kernel(弹 UAC)。
+ * 停止当前 Kernel → ShellExecute "runas" → 等待 ready → 重新订阅 SSE。
+ * 提权后进程不归 ProcessManager 管,停止走 /shutdown 接口。
+ */
+ async function startElevated() {
+ if (starting.value) return
+ starting.value = true
+ errorMsg.value = null
+ try {
+ await invoke('monitor_start_elevated')
+ await refreshStatus()
+ } catch (e) {
+ errorMsg.value = String(e)
+ logger.error('提权启动失败: ' + e)
+ } finally {
+ starting.value = false
+ }
+ }
+
+ /** 永久提权:以管理员权限重启 Thing 自身,ThingHK 子进程会继承管理员权限。
+ * 非管理员时进程退出;已是管理员时仅设置标志并返回。 */
+ async function elevateSelf() {
+ if (starting.value) return
+ starting.value = true
+ errorMsg.value = null
+ try {
+ await invoke('monitor_elevate_self')
+ // 命令返回说明已是管理员(非管理员时进程已 exit,不会走到这里)
+ await refreshElevateOnLaunch()
+ } catch (e) {
+ errorMsg.value = String(e)
+ logger.error('永久提权失败: ' + e)
+ } finally {
+ starting.value = false
+ }
+ }
+
+ /** 刷新永久提权标志状态 */
+ async function refreshElevateOnLaunch() {
+ try {
+ elevateOnLaunch.value = await invoke('monitor_get_elevate_on_launch')
+ } catch (e) {
+ logger.error('获取提权标志失败: ' + e)
+ }
+ return elevateOnLaunch.value
+ }
+
+ /** 取消永久提权:清除标志,下次启动不再触发 UAC(当前会话权限不变) */
+ async function cancelElevation() {
+ try {
+ await invoke('monitor_set_elevate_on_launch', { enabled: false })
+ await refreshElevateOnLaunch()
+ } catch (e) {
+ errorMsg.value = String(e)
+ logger.error('取消提权失败: ' + e)
+ }
+ }
+
+ /** 拉取硬件监控配置(可用硬件 + 传感器类型清单 + 当前启用状态) */
+ async function fetchHardwareConfig() {
+ try {
+ hardwareConfig.value = await invoke('monitor_get_hardware_config')
+ } catch (e) {
+ logger.error('获取硬件配置失败: ' + e)
+ }
+ return hardwareConfig.value
+ }
+
+ /** 保存硬件监控配置。
+ * 传感器类型过滤热生效;硬件开关变化返回 restartRequired=true。
+ * 调用方负责根据 restartRequired 决定是否重启 Kernel。 */
+ async function saveHardwareConfig(
+ hardware: Record,
+ sensorTypes: Record,
+ ): Promise {
+ try {
+ const resp = await invoke('monitor_set_hardware_config', {
+ hardware,
+ sensorTypes,
+ })
+ // 刷新本地缓存的配置
+ await fetchHardwareConfig()
+ return resp
+ } catch (e) {
+ errorMsg.value = String(e)
+ logger.error('保存硬件配置失败: ' + e)
+ return null
+ }
+ }
+
/** 主动拉取一次性快照(切回 tab 时立即填充,不等下一个 SSE tick) */
async function fetchSnapshot() {
try {
@@ -196,7 +339,8 @@ export const useMonitorStore = defineStore('monitor', () => {
refreshStatus()
}))
unlistenFns.push(await listen('monitor-loading', () => {
- // 状态由 status 轮询反映
+ // 后端正在等待 Kernel ready,刷新状态以反映 running=true
+ refreshStatus()
}))
unlistenFns.push(await listen('monitor-disconnected', () => {
logger.warn('SSE 断开,等待自动重连')
@@ -206,6 +350,10 @@ export const useMonitorStore = defineStore('monitor', () => {
errorMsg.value = e.payload?.message ?? 'Kernel 错误'
logger.error('Kernel 错误: ' + JSON.stringify(e.payload))
}))
+ // 网速监控事件(独立于 Kernel,应用启动即推送)
+ unlistenFns.push(await listen('monitor-network', (e) => {
+ networkSpeed.value = e.payload
+ }))
}
function unsubscribe() {
@@ -213,13 +361,29 @@ export const useMonitorStore = defineStore('monitor', () => {
unlistenFns = []
}
- /** 模块挂载时调用:刷新状态 + 订阅事件 + 拉取一次快照 */
+ /** 模块挂载时调用:刷新状态 + 订阅事件 + 拉取一次快照。
+ * 首次加载期间 initialized=false,UI 显示 loading 占位(隐藏启动按钮等),
+ * 避免与后端 setup 异步自动启动竞态导致按钮误显示。 */
async function init() {
- await Promise.all([refreshStatus(), refreshKernelInfo()])
- await subscribe()
- // 若 Kernel 已就绪,立即拉一次快照避免 UI 空白
- if (status.value?.ready) {
- await fetchSnapshot()
+ try {
+ await Promise.all([refreshStatus(), refreshKernelInfo(), refreshElevateOnLaunch()])
+ await subscribe()
+ // 若 Kernel 已就绪,立即拉一次快照避免 UI 空白
+ if (status.value?.ready) {
+ await fetchSnapshot()
+ }
+ // 自动启动竞态修复:setup 中 start_with_subscription 是异步 spawn,
+ // 首次 refreshStatus 可能返回 running=false(进程还未拉起)。
+ // 若状态仍为 idle,短暂重试以等待自动启动生效。
+ if (!status.value?.running) {
+ for (let i = 0; i < 5; i++) {
+ await new Promise(r => setTimeout(r, 500))
+ await refreshStatus()
+ if (status.value?.running) break
+ }
+ }
+ } finally {
+ initialized.value = true
}
}
@@ -239,6 +403,9 @@ export const useMonitorStore = defineStore('monitor', () => {
starting,
stopping,
initialized,
+ networkSpeed,
+ elevateOnLaunch,
+ hardwareConfig,
// getters
connState,
isLive,
@@ -247,7 +414,13 @@ export const useMonitorStore = defineStore('monitor', () => {
findSensorValue,
refreshStatus,
refreshKernelInfo,
+ refreshElevateOnLaunch,
start,
+ startElevated,
+ elevateSelf,
+ cancelElevation,
+ fetchHardwareConfig,
+ saveHardwareConfig,
stop,
fetchSnapshot,
init,