ODIAN Launcher: hub, installer, local stack and remembered sign-in (M2)
- Tauri 2 + Svelte 5 hub in ODIAN dark: patch notes and roadmap, install and update progress, local stack status, settings with DHP and MCP addresses. - crates/odian-packs: Rust installer for pack format v1 (signed manifests, resumable verified downloads from a DHP or an ODIAN drive), byte-compatible with ODIAN_MCP_Server's Python odian_packs. - Local stack on 127.0.0.1: Postgres -> Engine API -> tile server -> Odian.exe, one Job Object, clean shutdown; database snapshot restore on a fresh PC only. - Keycloak sign-in through the DHP's MCP, remembered until Sign out with a launcher session that renews the Engine token silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
//! Tauri commands: thin wrappers over `Launcher` (see docs/CONTRACT.md).
|
||||
|
||||
use tauri::{AppHandle, State};
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
use crate::launcher::{Launcher, Status};
|
||||
use crate::settings::Settings;
|
||||
use crate::stack::StackEvent;
|
||||
|
||||
type CmdResult<T> = Result<T, String>;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct SignedIn {
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_status(launcher: State<'_, Launcher>) -> CmdResult<Status> {
|
||||
Ok(launcher.status().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn refresh(app: AppHandle, launcher: State<'_, Launcher>) -> CmdResult<Status> {
|
||||
Ok(launcher.refresh(&app).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sign_in(app: AppHandle, launcher: State<'_, Launcher>) -> CmdResult<SignedIn> {
|
||||
launcher.sign_in(&app).await.map(|username| SignedIn { username })
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn cancel_sign_in(launcher: State<'_, Launcher>) -> CmdResult<()> {
|
||||
launcher.cancel_sign_in();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sign_out(app: AppHandle, launcher: State<'_, Launcher>) -> CmdResult<Status> {
|
||||
Ok(launcher.sign_out(&app).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_notes(launcher: State<'_, Launcher>, version: Option<String>) -> CmdResult<String> {
|
||||
launcher.notes(version).await
|
||||
}
|
||||
|
||||
/// Links in patch notes open in the system browser; the launcher window never navigates.
|
||||
#[tauri::command]
|
||||
pub async fn open_external(app: AppHandle, url: String) -> CmdResult<()> {
|
||||
let parsed = url::Url::parse(&url).map_err(|_| "That link is not a valid address.".to_string())?;
|
||||
if !matches!(parsed.scheme(), "http" | "https") {
|
||||
return Err("Only web links can be opened.".to_string());
|
||||
}
|
||||
app
|
||||
.opener()
|
||||
.open_url(parsed.as_str(), None::<&str>)
|
||||
.map_err(|e| format!("cannot open the browser: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_install(app: AppHandle, launcher: State<'_, Launcher>) -> CmdResult<()> {
|
||||
launcher.start_install(&app, None).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_install_from_drive(app: AppHandle, launcher: State<'_, Launcher>, path: String) -> CmdResult<()> {
|
||||
launcher.start_install(&app, Some(std::path::PathBuf::from(path))).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn cancel_install(launcher: State<'_, Launcher>) -> CmdResult<()> {
|
||||
launcher.cancel_install()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn launch(app: AppHandle, launcher: State<'_, Launcher>) -> CmdResult<()> {
|
||||
launcher.launch(&app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn stop(app: AppHandle, launcher: State<'_, Launcher>) -> CmdResult<()> {
|
||||
launcher.stop(&app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_stack(launcher: State<'_, Launcher>) -> CmdResult<Vec<StackEvent>> {
|
||||
Ok(launcher.stack_states())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_settings(launcher: State<'_, Launcher>) -> CmdResult<Settings> {
|
||||
Ok(launcher.settings().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_settings(app: AppHandle, launcher: State<'_, Launcher>, settings: Settings) -> CmdResult<Status> {
|
||||
launcher.set_settings(&app, settings).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn pick_folder(app: AppHandle) -> CmdResult<Option<String>> {
|
||||
// The native dialog blocks its thread, so keep it off the async runtime's workers.
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
app
|
||||
.dialog()
|
||||
.file()
|
||||
.blocking_pick_folder()
|
||||
.and_then(|picked| picked.into_path().ok())
|
||||
.map(|path| path.display().to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
Reference in New Issue
Block a user