use holger_ui::app::HolgerUiApp;
use holger_ui::data::UiData;
fn main() -> eframe::Result<()> {
let mut endpoint = "http://127.0.0.1:50051".to_string();
let mut token = std::env::var("HOLGER_TOKEN").ok();
let mut ca_path: Option<String> = None;
let mut cert_path: Option<String> = None;
let mut key_path: Option<String> = None;
let mut demo = false;
let mut demo_offline = false;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--token" => token = args.next(),
"--ca" => ca_path = args.next(),
"--cert" => cert_path = args.next(),
"--key" => key_path = args.next(),
"--demo" => demo = true,
"--demo-offline" => {
demo = true;
demo_offline = true;
}
other => endpoint = other.to_string(),
}
}
if demo {
return run_demo(!demo_offline);
}
let read_pem = |path: &str| -> Result<Vec<u8>, ()> {
std::fs::read(path).map_err(|e| {
eprintln!("holger-ui: failed to read PEM file {path}: {e}");
})
};
let client_identity = match (cert_path.as_deref(), key_path.as_deref()) {
(Some(c), Some(k)) => match (read_pem(c), read_pem(k)) {
(Ok(cert), Ok(key)) => Some((cert, key)),
_ => std::process::exit(1),
},
(None, None) => None,
_ => {
eprintln!("holger-ui: --cert and --key must be supplied together for mTLS");
std::process::exit(1);
}
};
let ca = match ca_path.as_deref() {
Some(p) => match read_pem(p) {
Ok(bytes) => Some(bytes),
Err(()) => std::process::exit(1),
},
None => None,
};
let use_tls = ca.is_some() || client_identity.is_some();
let connect = if use_tls {
UiData::connect_remote_with_tls(&endpoint, ca, client_identity, token.as_deref())
} else {
UiData::connect_remote_with_token(&endpoint, token.as_deref())
};
let data = match connect {
Ok(d) => d,
Err(e) => {
eprintln!("holger-ui: failed to connect to holger at {endpoint}: {e:#}");
std::process::exit(1);
}
};
let native_options = eframe::NativeOptions {
viewport: eframe::egui::ViewportBuilder::default()
.with_inner_size([1100.0, 720.0])
.with_min_inner_size([720.0, 480.0])
.with_title("holger"),
..Default::default()
};
eframe::run_native(
"holger-ui",
native_options,
Box::new(move |cc| {
let mut app = HolgerUiApp::new(data);
app.set_session_client_identity(
cert_path.map(std::path::PathBuf::from),
key_path.map(std::path::PathBuf::from),
);
app.apply_look(&cc.egui_ctx);
Ok(Box::new(app))
}),
)
}
fn run_demo(allow_network: bool) -> eframe::Result<()> {
use std::sync::Arc;
let store_dir = std::env::temp_dir().join(format!("holger-demo-store-{}", std::process::id()));
let fill = match holger_ui::demo::fill_demo_store(&store_dir, allow_network) {
Ok(f) => f,
Err(e) => {
eprintln!("holger-ui --demo: failed to fill demo store: {e:#}");
std::process::exit(1);
}
};
eprintln!(
"holger-ui --demo: filled {} packages across {} repos ({}) → store {}",
fill.packages.len(),
fill.repos.len(),
if fill.online { format!("{} real from crates.io", fill.real_count()) } else { "offline fixture".to_string() },
store_dir.display(),
);
let local = fill.local_holger();
let data = match UiData::new(Arc::new(local)) {
Ok(d) => d,
Err(e) => {
eprintln!("holger-ui --demo: failed to build view-model: {e:#}");
std::process::exit(1);
}
};
let native_options = eframe::NativeOptions {
viewport: eframe::egui::ViewportBuilder::default()
.with_inner_size([1100.0, 720.0])
.with_min_inner_size([720.0, 480.0])
.with_title("holger — demo"),
..Default::default()
};
eframe::run_native(
"holger-ui-demo",
native_options,
Box::new(move |cc| {
let mut app = HolgerUiApp::new(data);
let beat_ms = std::env::var("HOLGER_DEMO_BEAT_MS")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(2200);
app.enable_demo(beat_ms);
app.apply_look(&cc.egui_ctx);
Ok(Box::new(app))
}),
)
}