feat: 补齐预设与变量注入(遗嘱模板注入、v5 文本属性注入、删除预设二次确认)
This commit is contained in:
@@ -526,6 +526,23 @@ pub fn render_template(input: &str, vars: &[GlobalVariable]) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
/// 连接前渲染遗嘱消息中的 `{{变量}}`。
|
||||
/// 只作用于本次连接的配置副本,落盘的连接配置保持模板原文,
|
||||
/// 这样改全局变量后下次重连即生效,`$ts` 等也按连接时刻求值。
|
||||
pub fn render_will_templates(cfg: &mut ConnectionConfig, vars: &[GlobalVariable]) {
|
||||
let Some(will) = cfg.last_will.as_mut() else {
|
||||
return;
|
||||
};
|
||||
will.topic = render_template(&will.topic, vars);
|
||||
will.payload = render_template(&will.payload, vars);
|
||||
if let Some(v) = will.content_type.take() {
|
||||
will.content_type = Some(render_template(&v, vars));
|
||||
}
|
||||
if let Some(v) = will.response_topic.take() {
|
||||
will.response_topic = Some(render_template(&v, vars));
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 日志 ────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -729,6 +746,44 @@ mod tests {
|
||||
assert_eq!(render_template("中文{{$no}}", &[]), "中文{{$no}}");
|
||||
}
|
||||
|
||||
// 连接前遗嘱模板注入:只改副本、空遗嘱不 panic、v5 属性一并渲染
|
||||
#[test]
|
||||
fn render_will_templates_injects_and_keeps_no_will_safe() {
|
||||
let vars = vec![GlobalVariable {
|
||||
key: "device".into(),
|
||||
value: "sensor-9".into(),
|
||||
}];
|
||||
// 无遗嘱:直接返回
|
||||
let mut plain = ConnectionConfig::new();
|
||||
render_will_templates(&mut plain, &vars);
|
||||
assert!(plain.last_will.is_none());
|
||||
|
||||
let mut cfg = ConnectionConfig::new();
|
||||
cfg.last_will = Some(LastWill {
|
||||
topic: "alarm/{{device}}".into(),
|
||||
payload: "d={{device}}".into(),
|
||||
qos: 1,
|
||||
retain: false,
|
||||
content_type: Some("text/{{device}}".into()),
|
||||
response_topic: Some("ack/{{device}}".into()),
|
||||
});
|
||||
render_will_templates(&mut cfg, &vars);
|
||||
let w = cfg.last_will.as_ref().unwrap();
|
||||
assert_eq!(w.topic, "alarm/sensor-9");
|
||||
assert_eq!(w.payload, "d=sensor-9");
|
||||
assert_eq!(w.content_type.as_deref(), Some("text/sensor-9"));
|
||||
assert_eq!(w.response_topic.as_deref(), Some("ack/sensor-9"));
|
||||
// 原始模板串不因注入而需要回写:连接配置持久化的是模板原文,
|
||||
// 这里只验证副本注入结果;未知占位符保留
|
||||
let mut raw = ConnectionConfig::new();
|
||||
raw.last_will = Some(LastWill {
|
||||
topic: "t/{{unknown}}".into(),
|
||||
..Default::default()
|
||||
});
|
||||
render_will_templates(&mut raw, &vars);
|
||||
assert_eq!(raw.last_will.as_ref().unwrap().topic, "t/{{unknown}}");
|
||||
}
|
||||
|
||||
// 预设持久化往返:raw_bytes 不落盘,参数完整保留
|
||||
#[test]
|
||||
fn publish_preset_roundtrip_skips_raw_bytes() {
|
||||
|
||||
@@ -15,7 +15,7 @@ use gpui_kit::{
|
||||
};
|
||||
|
||||
use crate::aliyun::{AliyunAuthMode, AliyunPreset};
|
||||
use crate::model::ConnectionConfig;
|
||||
use crate::model::{render_will_templates, ConnectionConfig};
|
||||
use crate::ui::app::MqttXApp;
|
||||
use crate::ui::widgets::{field, make_select, OptionDelegate};
|
||||
|
||||
@@ -137,6 +137,8 @@ impl AliyunDialog {
|
||||
conn.name = preset.name.clone();
|
||||
self.app.update(cx, |a, cx| {
|
||||
a.save_connection(conn.clone());
|
||||
// 遗嘱字段同样走 {{变量}} 注入(与其它连接入口保持一致)
|
||||
render_will_templates(&mut conn, &a.variables);
|
||||
a.engine.connect(conn);
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
@@ -487,7 +487,10 @@ impl MqttXApp {
|
||||
if self.engine.is_connected(&cfg.id) {
|
||||
self.engine.close(&cfg.id, true);
|
||||
} else {
|
||||
self.engine.connect(cfg.clone());
|
||||
// 遗嘱支持 {{变量}}:对副本注入,保存的配置仍为模板原文
|
||||
let mut cfg = cfg.clone();
|
||||
crate::model::render_will_templates(&mut cfg, &self.variables);
|
||||
self.engine.connect(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ use gpui_kit::{
|
||||
use gpui_kit::component::IndexPath;
|
||||
|
||||
use crate::model::{
|
||||
render_template, ConnectionConfig, ConnectionStatus, Direction, LogLevel, MqttRecord,
|
||||
render_template, render_will_templates, ConnectionConfig, ConnectionStatus, Direction, LogLevel, MqttRecord,
|
||||
PayloadFormat, PublishParams, SubscribeOptions, Subscription,
|
||||
};
|
||||
use crate::mqtt::MqttEngine;
|
||||
@@ -588,6 +588,10 @@ impl ConnectionView {
|
||||
.into_iter()
|
||||
.map(|(k, v)| (render_template(&k, &vars), render_template(&v, &vars)))
|
||||
.collect();
|
||||
// v5 文本属性(Content-Type / Response Topic / Correlation Data)一并注入
|
||||
params.content_type = params.content_type.map(|v| render_template(&v, &vars));
|
||||
params.response_topic = params.response_topic.map(|v| render_template(&v, &vars));
|
||||
params.correlation_data = params.correlation_data.map(|v| render_template(&v, &vars));
|
||||
|
||||
if let PayloadFormat::Json = params.payload_format
|
||||
&& let Err(e) = serde_json::from_str::<serde_json::Value>(¶ms.payload) {
|
||||
@@ -794,10 +798,15 @@ impl ConnectionView {
|
||||
return;
|
||||
}
|
||||
let cfg = this.config(cx);
|
||||
if let Some(cfg) = cfg {
|
||||
if let Some(mut cfg) = cfg {
|
||||
if this.engine.is_connected(&cfg.id) {
|
||||
this.engine.close(&cfg.id, true);
|
||||
} else {
|
||||
// 遗嘱 {{变量}} 在每次连接时按当前全局变量求值
|
||||
let vars = this
|
||||
.with_app(cx, |app| app.variables.clone())
|
||||
.unwrap_or_default();
|
||||
render_will_templates(&mut cfg, &vars);
|
||||
this.engine.connect(cfg);
|
||||
}
|
||||
}
|
||||
@@ -2050,20 +2059,44 @@ impl ConnectionView {
|
||||
for p in &del_presets {
|
||||
let weak2 = weak_del.clone();
|
||||
let id = p.id.clone();
|
||||
let name = p.name.clone();
|
||||
let label = format!("删除「{}」", p.name);
|
||||
submenu = submenu.item(
|
||||
PopupMenuItem::new(SharedString::from(label)).on_click(
|
||||
move |_ev, _w, cx| {
|
||||
if let Some(view) = weak2.upgrade() {
|
||||
view.update(cx, |view, cx| {
|
||||
move |_ev, window, cx| {
|
||||
// 预设删除不可撤销,先弹二次确认
|
||||
let weak = weak2.clone();
|
||||
let pid = id.clone();
|
||||
let pname = name.clone();
|
||||
window.open_alert_dialog(cx, move |alert, _, _| {
|
||||
let weak = weak.clone();
|
||||
let pid = pid.clone();
|
||||
alert
|
||||
.title("删除预设")
|
||||
.description(format!(
|
||||
"确定删除预设「{pname}」吗?此操作不可撤销。"
|
||||
))
|
||||
.button_props(
|
||||
DialogButtonProps::default()
|
||||
.show_cancel(true)
|
||||
.cancel_text("取消")
|
||||
.ok_text("删除")
|
||||
.ok_variant(ButtonVariant::Danger),
|
||||
)
|
||||
.on_ok(move |_, _, cx| {
|
||||
weak.update(cx, |view, cx| {
|
||||
if let Some(app) = view.app.upgrade() {
|
||||
app.update(cx, |app, cx| {
|
||||
app.delete_publish_preset(&id);
|
||||
app.delete_publish_preset(&pid);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user