ek-ui 0.9.21

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
Documentation
//! 一次性运维小工具:改**已在册**账户段的 td 前置(并同步行情侧凭据/前置)。
//!
//! 场景(0.9.10 全链审计):station 闸夹具把真账户档案的 td 前置覆盖成夹具值
//! (127.0.0.1:30001),本工具把档案改回真前置。**派生名纪律注意**:档案名按
//! (broker,账号,td) 唯一化(0.9.10 后缀式),改 td 后若与另一在册行三全同 = 拒绝
//! (该档本就该是那一行);改回自己 = 合法。md 侧:给出 md 则同步 md_addr/凭据
//! (同账号同密口径)。密码明文只过内存,不落打印。

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    let (Some(gw), Some(td_addr)) = (args.first(), args.get(1)) else {
        eprintln!("用法: set_td <网关名> <td_addr> [md_addr]");
        std::process::exit(2);
    };
    let md_addr = args.get(2).cloned().unwrap_or_default();
    let ns = format!("ctp.{gw}");
    let store = ek_cfg::Store::eco().expect("档案根");
    let map0 = match store.load(&ns) {
        Ok(ek_cfg::LoadOutcome::Loaded { map, .. }) => map,
        Ok(ek_cfg::LoadOutcome::Fresh) => {
            eprintln!("段 {ns} 不存在");
            std::process::exit(1);
        }
        Err(e) => {
            eprintln!("段读不动: {e:?}");
            std::process::exit(1);
        }
    };
    let broker = map0.get_plain("broker_id").unwrap_or_default().to_string();
    let user = map0.get_plain("td_user").unwrap_or_default().to_string();
    // 唯一键核验(与写口同算法):改后不得与**其它行**三全同。
    let mut taken: Vec<(String, String, String, String)> = Vec::new();
    for nsx in store.list_namespaces().unwrap_or_default() {
        if let Some(gwx) = nsx.strip_prefix("ctp.")
            && let Ok(ek_cfg::LoadOutcome::Loaded { map: mx, .. }) = store.load(&nsx)
        {
            taken.push((
                gwx.to_string(),
                map0get(&mx, "broker_id"),
                map0get(&mx, "td_user"),
                map0get(&mx, "td_addr"),
            ));
        }
    }
    taken.retain(|(gwx, _, _, _)| gwx != gw);
    if let Err(e) = ek_ui::appconfig::unique_gw_name(&taken, &broker, &user, td_addr) {
        eprintln!("改口拒绝: {e}");
        std::process::exit(1);
    }
    let pw = ek_cfg::secret::get(&ns, "gw_td_password")
        .map(|v| String::from_utf8_lossy(v.as_bytes()).to_string())
        .unwrap_or_default();
    if pw.is_empty() {
        eprintln!("本段密库无 gw_td_password");
        std::process::exit(1);
    }
    let mut map = map0.set("td_addr", ek_core::Field::plain(td_addr.trim()));
    if md_addr.is_empty() {
        // 无 md:行情侧键全撤(段 = 唯一事实,不留幽灵键)。
        map = map.set("md_addr", ek_core::Field::plain(String::new()));
    } else {
        ek_cfg::secret::register(
            &ns,
            "gw_md_password",
            ek_cfg::SecretValue::from_bytes(pw),
        )
        .expect("md 密库登记");
        map = map
            .set("md_addr", ek_core::Field::plain(md_addr.trim()))
            .set("md_user", ek_core::Field::plain(user.trim()))
            .set("md_password", ek_core::Field::secret("gw_md_password"));
    }
    store.save(&ns, &map).expect("段存档");
    println!("已改档 {ns}: td_addr={td_addr}{}", if md_addr.is_empty() { " (md 撤)" } else { " , md 已同步" });
}

fn map0get(m: &ek_core::SectionMap, k: &str) -> String {
    m.get_plain(k).unwrap_or_default().to_owned()
}