feat(app-iced): M95 日志页接真组件 + 诊断面板(等宽折行 / 按严重度分组 / 一行摘要)
日志页从 M84 占位换成真组件,数据全部来自 presenter 快照(页面不读盘):
- 日志行列表:`pages/logs/lines.rs`,等宽(`BodySmall` 12/16 + mono 族)+ 按词或字形
折行 + 等级色条;**选折行不选横滚**的理由写在模块文档(424px × 200 字符的现实 +
mono 族的 CJK 回退会毁掉横滚唯一的收益「列对齐」)。
- 诊断面板:`pages/logs/diagnostic.rs`,按严重度分组(CRITICAL → WARNING → NOTICE),
每条给规则 id / 结论 / 说明 / 建议动作 / 置信度;卡片头钉住「Copy summary」,
摘要是稳定单行的 `ULL-LOGS v1 | …`(崩溃日志换 issue 时直接粘)。
- 空态:真组件 `feedback::EmptyState`(图标块 + 标题 + 说明 + CTA 回实例页)。
- 两块都在 `pages/logs/panel.rs` 里**定高 + 内部独立滚动**(`FillPortion(3):(2)`):
第一版让诊断卡按内容撑开,三条结论就把日志卡压成 0 高度(实拍复现后改的)。
- 视图模型:`pages/logs/model.rs`(纯展示字段)+ `app/view.rs::logs_model` 做快照转换。
- 接线(只追加):`pages/mod.rs` 的 `pub use logs::…` / `page()` 那行 /
`Message::CopySummary` / `demo_model` 一行;`pages::Model` 加 `logs` 与 `lang` 两个字段;
`app/update.rs` 把 `CopySummary` 落到 `iced::clipboard::write`、`ClearLogs` 清 presenter 的
`log_lines`(呈现层操作,不造命令)。
- i18n:`crates/i18n/src/catalog.rs` 自己锚点下 16 个 key(中英同表);按塔裁决把
`#[cfg(test)] mod tests` 逐字外置到 `crates/i18n/src/catalog/tests.rs`
(catalog.rs 415 → 257 行,tests.rs 164 行;7 个用例逐字未动、i18n 23 个用例全绿)。
验证:`fmt --check` / `clippy -p unlucky-app-iced --all-targets -- -D warnings` 干净;
`test -p unlucky-app-iced` 272 + 6 全绿、`-p unlucky-i18n` 23 全绿;截图
`.screens/logs-{empty,crash,live}-{light,dark}.png`(1180×760,本地 gitignore)。
遗留(已报 finding):`dto::Diagnosis` 缺 core 的 `severity` 与规则表 `cause.key`;
`Action::OpenLogs` 没有对应 `AppCommand`(点了只有占位日志)。
This commit is contained in:
1 parent
c0396544c5
commit
063a1dba39
16 files changed
+1392
-157
No files matched your search
@@ -0,0 +1,153 @@
|
||||
//! 日志页演示数据:**只给 `pages_demo` 出图与单测**,产品运行时的数据来自 presenter 快照
|
||||
//! (`app/view.rs::logs_model`)。页面任何路径都不读盘:日志文本要么来自快照,要么来自
|
||||
//! 本文件 `include_str!` 内嵌的 [`SAMPLE`](编译期进二进制,运行时零 I/O)。
|
||||
//!
|
||||
//! ## 为什么用环境变量挑状态
|
||||
//!
|
||||
//! 三种状态(空态 / 有日志 / 有诊断)要分别出图,而 `pages_demo` 的 `--screen` 只有一个
|
||||
//! `logs`。环境变量是这个 crate 已有的 harness 口径(`ULL_GALLERY_SHOT` / `ULL_FEEDBACK_PART`
|
||||
//! 都是这么挑的,见 `components/gallery/mod.rs` 的说明):既能被测试直接调纯函数覆盖,
|
||||
//! 又不用跟别人的 CLI 解析抢同一个文件。
|
||||
//!
|
||||
//! ```text
|
||||
//! ULL_LOGS_FIXTURE=empty pages_demo --screen=logs --screenshot=logs-empty-light.png
|
||||
//! ```
|
||||
//!
|
||||
//! 取值:`empty`(空态)/ `live`(有日志、无诊断)/ 其它或未设 = `crash`(日志 + 三条诊断)。
|
||||
//! 默认取 `crash`:一屏就能看见页面的全部结构,reviewer 不必先猜环境变量。
|
||||
//!
|
||||
//! ## 样例内容的出处
|
||||
//!
|
||||
//! [`SAMPLE`] 是按 `core/diagnose/rules.d/samples/graphics/graphics_driver.log` 与
|
||||
//! `integrity/unsatisfied_link.log` 的形状写的(含中文 + 堆栈 + 200 字符以上的长行),
|
||||
//! 诊断结论的 `rule` / `title` / `hint` 抄自规则表 `rules.d/integrity.yaml` 与 `graphics.yaml`。
|
||||
//! **中文在这里是「规则表的输出(数据)」,不是界面文案**:界面文案(标题 / 按钮 / 分组名 /
|
||||
//! 空态)全部走 `unlucky_i18n` 的 key,见 `logs.rs`。
|
||||
|
||||
use super::model::{Finding, Level, Line, Logs, Outcome, Severity};
|
||||
|
||||
/// 挑演示状态的环境变量。
|
||||
pub const STATE_ENV: &str = "ULL_LOGS_FIXTURE";
|
||||
|
||||
/// 内嵌的崩溃日志片段(见模块文档的出处)。
|
||||
const SAMPLE: &str = include_str!("sample_crash.log");
|
||||
|
||||
/// 演示状态。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum State {
|
||||
/// 空态:没有输出,也没有诊断。
|
||||
Empty,
|
||||
/// 有日志、无诊断(正常跑着的一场)。
|
||||
Live,
|
||||
/// 崩溃现场:日志 + 诊断结论。
|
||||
Crash,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// 从环境变量取值解析(未设 / 不认识一律 `Crash`)。
|
||||
#[must_use]
|
||||
pub fn of_env_value(raw: Option<&str>) -> Self {
|
||||
match raw {
|
||||
Some("empty") => Self::Empty,
|
||||
Some("live") => Self::Live,
|
||||
_ => Self::Crash,
|
||||
}
|
||||
}
|
||||
|
||||
/// 造出这个状态的视图模型。
|
||||
#[must_use]
|
||||
pub fn build(self) -> Logs {
|
||||
match self {
|
||||
Self::Empty => Logs::default(),
|
||||
Self::Live => Logs {
|
||||
outcome: Some(Outcome::Ok),
|
||||
findings: Vec::new(),
|
||||
..crash()
|
||||
},
|
||||
Self::Crash => crash(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// harness 入口:按 [`STATE_ENV`] 挑状态。
|
||||
#[must_use]
|
||||
pub fn demo() -> Logs {
|
||||
State::of_env_value(std::env::var(STATE_ENV).ok().as_deref()).build()
|
||||
}
|
||||
|
||||
/// 崩溃现场(日志 + 三条诊断,覆盖 critical / warning 两个分组)。
|
||||
#[must_use]
|
||||
pub fn crash() -> Logs {
|
||||
Logs {
|
||||
instance: Some("开发测试".to_owned()),
|
||||
when: Some("11-02 19:40".to_owned()),
|
||||
outcome: Some(Outcome::Crashed),
|
||||
lines: parse(SAMPLE),
|
||||
findings: findings(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析内嵌样例:`#` 注释与空行跳过,首字符是等级前缀。
|
||||
fn parse(text: &str) -> Vec<Line> {
|
||||
text.lines()
|
||||
.filter_map(|raw| {
|
||||
let raw = raw.trim_end();
|
||||
if raw.is_empty() || raw.starts_with('#') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (prefix, rest) = raw.split_at(1);
|
||||
let level = match prefix {
|
||||
"D" => Level::Debug,
|
||||
"I" => Level::Info,
|
||||
"W" => Level::Warn,
|
||||
"E" => Level::Error,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(Line {
|
||||
text: rest.trim_start().to_owned(),
|
||||
level,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 三条诊断(`rule` / `title` / `hint` 抄自规则表;`severity` 走 [`Severity::of_confidence`],
|
||||
/// 也就是 DTO 缺 `severity` 时产品路径用的同一条折算)。
|
||||
fn findings() -> Vec<Finding> {
|
||||
vec![
|
||||
finding(
|
||||
"integrity.unsatisfied_link",
|
||||
85,
|
||||
"找不到原生库(natives):lwjgl_opengl.dll 没解压出来或被杀软删了",
|
||||
"重装组件(重新解压 natives);Windows 上检查杀软隔离区",
|
||||
Some("reinstall natives"),
|
||||
),
|
||||
finding(
|
||||
"graphics.gl_operation_failure",
|
||||
70,
|
||||
"某个 mod/光影包做了非法的 OpenGL 操作(GL_INVALID_OPERATION)",
|
||||
"换掉最近装的光影/资源包,或把资源包分辨率降下来",
|
||||
Some("change_graphics.renderer = OpenGL 兼容模式"),
|
||||
),
|
||||
finding(
|
||||
"integrity.no_class_def_found",
|
||||
60,
|
||||
"NoClassDefFoundError:缺少类文件(库没下全 / jar 被解压 / mod 不配套)",
|
||||
"先检查 libraries 是否完整,再排查 mods 里的解压目录",
|
||||
Some("reinstall libraries"),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn finding(rule: &str, confidence: u8, title: &str, hint: &str, action: Option<&str>) -> Finding {
|
||||
Finding {
|
||||
rule: rule.to_owned(),
|
||||
severity: Severity::of_confidence(confidence),
|
||||
title: title.to_owned(),
|
||||
hint: hint.to_owned(),
|
||||
confidence,
|
||||
action: action.map(str::to_owned),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user