import multiple custom bsod

This commit is contained in:
Julian Freeman
2025-11-26 09:58:09 -04:00
parent 936c58dcc8
commit 1ad180a413
2 changed files with 63 additions and 35 deletions

View File

@@ -7,12 +7,14 @@
use serde::Serialize; use serde::Serialize;
use sysinfo::{System, Disks}; use sysinfo::{System, Disks};
use wmi::{COMLibrary, WMIConnection}; use wmi::{COMLibrary, WMIConnection};
use std::{fs, ops::Deref}; use std::fs;
use std::path::Path; use std::path::Path;
use tauri::Emitter; use tauri::Emitter;
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime}; use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime};
// 只引入 minidump 基础库 // 只引入 minidump 基础库
use minidump::{Minidump, MinidumpException, MinidumpSystemInfo}; use minidump::{Minidump, MinidumpException, MinidumpSystemInfo};
// [新增] 引入 Deref 用于泛型约束
use std::ops::Deref;
// --- 1. 数据结构 (保持不变) --- // --- 1. 数据结构 (保持不变) ---
@@ -188,8 +190,10 @@ fn translate_bugcheck_u32(code: u32) -> (String, String) {
} }
} }
// [新增] 抽离公共分析逻辑 // [修复] 抽离公共分析逻辑,使用泛型 T 适配不同数据源
fn analyze_dump_data<T: Deref<Target = [u8]>>(dump: Minidump<T>) -> Result<BsodAnalysisReport, String> { fn analyze_dump_data<T>(dump: Minidump<T>) -> Result<BsodAnalysisReport, String>
where T: Deref<Target = [u8]>
{
let exception_stream = dump.get_stream::<MinidumpException>() let exception_stream = dump.get_stream::<MinidumpException>()
.map_err(|_| "无法找到异常信息流 (No Exception Stream),可能是非标准 Dump 文件。".to_string())?; .map_err(|_| "无法找到异常信息流 (No Exception Stream),可能是非标准 Dump 文件。".to_string())?;

View File

@@ -165,8 +165,8 @@
</div> </div>
</div> </div>
<!-- 隐藏的 BSOD 文件输入框 --> <!-- 隐藏的 BSOD 文件输入框 (添加 multiple) -->
<input type="file" ref="bsodFileInput" @change="handleBsodFileImport" accept=".dmp" style="display: none" /> <input type="file" ref="bsodFileInput" @change="handleBsodFileImport" accept=".dmp" style="display: none" multiple />
<div class="bsod-layout"> <div class="bsod-layout">
<div class="bsod-list-panel"> <div class="bsod-list-panel">
@@ -178,10 +178,10 @@
v-for="file in bsodList" v-for="file in bsodList"
:key="file.path" :key="file.path"
class="file-item" class="file-item"
:class="{ active: selectedBsod?.path === file.path }" :class="{ active: selectedBsod?.path === file.path, imported: !!file.fileRef }"
@click="analyzeBsod(file)" @click="analyzeBsod(file)"
> >
<div class="file-icon">📄</div> <div class="file-icon">{{ file.fileRef ? '📨' : '📄' }}</div>
<div class="file-info"> <div class="file-info">
<div class="file-name">{{ file.filename }}</div> <div class="file-name">{{ file.filename }}</div>
<div class="file-meta">{{ file.created_time }} · {{ file.size_kb }}KB</div> <div class="file-meta">{{ file.created_time }} · {{ file.size_kb }}KB</div>
@@ -320,38 +320,58 @@ async function startScan() {
// --- BSOD 功能 --- // --- BSOD 功能 ---
async function loadMinidumps() { bsodLoading.value = true; try { bsodList.value = await invoke('list_minidumps'); } catch (e) { triggerToast('加载失败', e, 'error'); } finally { bsodLoading.value = false; } } async function loadMinidumps() { bsodLoading.value = true; try { bsodList.value = await invoke('list_minidumps'); } catch (e) { triggerToast('加载失败', e, 'error'); } finally { bsodLoading.value = false; } }
async function analyzeBsod(file) { if (bsodAnalyzing.value) return; selectedBsod.value = file; bsodResult.value = null; bsodAnalyzing.value = true; try { bsodResult.value = await invoke('analyze_minidump', { filepath: file.path }); } catch (e) { triggerToast('分析失败', e, 'error'); } finally { bsodAnalyzing.value = false; } } async function analyzeBsod(file) {
if (bsodAnalyzing.value) return;
// [新增] BSOD 导入功能 selectedBsod.value = file;
function triggerBsodImport() { bsodFileInput.value.click(); }
function handleBsodFileImport(event) {
const file = event.target.files[0];
if (!file) return;
// 更新 UI 状态,模拟选中了一个“外部文件”
selectedBsod.value = { path: 'external', filename: file.name, created_time: 'Imported', size_kb: Math.round(file.size / 1024) };
bsodResult.value = null; bsodResult.value = null;
bsodAnalyzing.value = true; bsodAnalyzing.value = true;
const reader = new FileReader(); try {
reader.onload = async (e) => { // 判断是导入的文件还是本地文件
try { if (file.fileRef) {
// ArrayBuffer 转换为 Uint8Array (Rust Vec<u8>) // 导入的文件:读取 ArrayBuffer 并传给后端
const arrayBuffer = e.target.result; const arrayBuffer = await file.fileRef.arrayBuffer();
const bytes = new Uint8Array(arrayBuffer); const bytes = new Uint8Array(arrayBuffer);
const byteArray = Array.from(bytes); // 转换为普通数组以便序列化传输 const byteArray = Array.from(bytes);
bsodResult.value = await invoke('analyze_minidump_bytes', { fileContent: byteArray });
const result = await invoke('analyze_minidump_bytes', { fileContent: byteArray }); } else {
bsodResult.value = result; // 本地文件:传路径
triggerToast('分析成功', '已完成外部文件解析', 'success'); bsodResult.value = await invoke('analyze_minidump', { filepath: file.path });
} catch (err) {
triggerToast('分析失败', err, 'error');
} finally {
bsodAnalyzing.value = false;
} }
}; } catch (e) {
reader.readAsArrayBuffer(file); triggerToast('分析失败', e, 'error');
event.target.value = ''; } finally {
bsodAnalyzing.value = false;
}
}
// [新增] BSOD 导入功能 (支持多选)
function triggerBsodImport() { bsodFileInput.value.click(); }
function handleBsodFileImport(event) {
const files = event.target.files;
if (!files || files.length === 0) return;
let importedCount = 0;
// 倒序遍历,这样添加到数组头部后顺序是正确的
for (let i = files.length - 1; i >= 0; i--) {
const file = files[i];
// 构造一个符合列表格式的虚拟对象
const newItem = {
filename: file.name,
// 使用一个特殊的 path 标识,防止 key 冲突
path: `imported-${file.name}-${Date.now()}-${i}`,
size_kb: Math.round(file.size / 1024),
// 使用浏览器读取到的修改时间
created_time: new Date(file.lastModified).toLocaleString(),
// 关键:保存文件引用,以便点击时读取
fileRef: file
};
bsodList.value.unshift(newItem);
importedCount++;
}
triggerToast('导入成功', `已添加 ${importedCount} 个文件到列表`, 'success');
event.target.value = ''; // 重置,允许再次导入同名文件
} }
watch(currentTab, (newVal) => { if (newVal === 'bsod' && bsodList.value.length === 0) loadMinidumps(); }); watch(currentTab, (newVal) => { if (newVal === 'bsod' && bsodList.value.length === 0) loadMinidumps(); });
@@ -443,7 +463,11 @@ body { margin: 0; padding: 0; background-color: #f4f6f9; overflow: hidden; }
.bsod-list-panel { background: white; border-radius: 10px; border: 1px solid #eaecf0; overflow-y: auto; } .bsod-list-panel { background: white; border-radius: 10px; border: 1px solid #eaecf0; overflow-y: auto; }
.bsod-detail-panel { background: white; border-radius: 10px; border: 1px solid #eaecf0; padding: 25px; overflow-y: auto; position: relative; } .bsod-detail-panel { background: white; border-radius: 10px; border: 1px solid #eaecf0; padding: 25px; overflow-y: auto; position: relative; }
.file-item { padding: 15px; border-bottom: 1px solid #f0f2f5; cursor: pointer; display: flex; gap: 12px; transition: background 0.2s; } .file-item { padding: 15px; border-bottom: 1px solid #f0f2f5; cursor: pointer; display: flex; gap: 12px; transition: background 0.2s; }
.file-item:hover { background: #f8f9fa; } .file-item.active { background: #eafaf1; border-left: 4px solid #2ecc71; } .file-item:hover { background: #f8f9fa; }
.file-item.active { background: #eafaf1; border-left: 4px solid #2ecc71; }
/* 给导入的文件加一点特殊样式区分(可选) */
.file-item.imported .file-name { color: #3498db; }
.file-icon { font-size: 1.5rem; } .file-name { font-weight: 600; font-size: 0.9rem; color: #2c3e50; margin-bottom: 4px; } .file-meta { font-size: 0.8rem; color: #95a5a6; } .file-icon { font-size: 1.5rem; } .file-name { font-weight: 600; font-size: 0.9rem; color: #2c3e50; margin-bottom: 4px; } .file-meta { font-size: 0.8rem; color: #95a5a6; }
.empty-state { padding: 40px; text-align: center; color: #95a5a6; font-size: 0.9rem; line-height: 1.6; } .empty-state { padding: 40px; text-align: center; color: #95a5a6; font-size: 0.9rem; line-height: 1.6; }
.analyzing-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #7f8c8d; } .analyzing-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #7f8c8d; }