feat: add unified Settings sheet, MCP indicator, and Docker host config
- Add AppSettingsSheet (gear icon in Toolbar) with MCP, Docker, and AI sections - MCP Server: toggle on/off, port config, status badge, endpoint URL with copy - Docker: local/remote daemon selector with remote URL input - AI: moved Ollama settings into the unified sheet - MCP status probes actual TCP port for reliable running detection - Docker commands respect configurable docker host (-H flag) for remote daemons - MCP server supports graceful shutdown via tokio watch channel - Settings persisted to app_settings.json alongside existing config files - StatusBar shows MCP indicator (green/gray dot) with tooltip Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
116
src-tauri/src/commands/settings.rs
Normal file
116
src-tauri/src/commands/settings.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use crate::error::{TuskError, TuskResult};
|
||||
use crate::mcp;
|
||||
use crate::models::settings::{AppSettings, DockerHost, McpStatus};
|
||||
use crate::state::AppState;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
|
||||
fn get_settings_path(app: &AppHandle) -> TuskResult<std::path::PathBuf> {
|
||||
let dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| TuskError::Custom(e.to_string()))?;
|
||||
fs::create_dir_all(&dir)?;
|
||||
Ok(dir.join("app_settings.json"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_app_settings(app: AppHandle) -> TuskResult<AppSettings> {
|
||||
let path = get_settings_path(&app)?;
|
||||
if !path.exists() {
|
||||
return Ok(AppSettings::default());
|
||||
}
|
||||
let data = fs::read_to_string(&path)?;
|
||||
let settings: AppSettings = serde_json::from_str(&data)?;
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_app_settings(
|
||||
app: AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
settings: AppSettings,
|
||||
) -> TuskResult<()> {
|
||||
let path = get_settings_path(&app)?;
|
||||
let data = serde_json::to_string_pretty(&settings)?;
|
||||
fs::write(&path, data)?;
|
||||
|
||||
// Apply docker host setting
|
||||
{
|
||||
let mut docker_host = state.docker_host.write().await;
|
||||
*docker_host = match settings.docker.host {
|
||||
DockerHost::Remote => settings.docker.remote_url.clone(),
|
||||
DockerHost::Local => None,
|
||||
};
|
||||
}
|
||||
|
||||
// Apply MCP setting: restart or stop
|
||||
let is_running = *state.mcp_running.read().await;
|
||||
|
||||
if settings.mcp.enabled {
|
||||
if is_running {
|
||||
// Stop existing MCP server first
|
||||
let _ = state.mcp_shutdown_tx.send(true);
|
||||
// Give it a moment to shut down
|
||||
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
||||
*state.mcp_running.write().await = false;
|
||||
}
|
||||
|
||||
// Start new MCP server
|
||||
let connections_path = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| TuskError::Custom(e.to_string()))?
|
||||
.join("connections.json");
|
||||
|
||||
let mcp_state = state.inner().clone();
|
||||
let port = settings.mcp.port;
|
||||
let shutdown_rx = state.mcp_shutdown_tx.subscribe();
|
||||
|
||||
tokio::spawn(async move {
|
||||
*mcp_state.mcp_running.write().await = true;
|
||||
if let Err(e) =
|
||||
mcp::start_mcp_server(mcp_state.clone(), connections_path, port, shutdown_rx).await
|
||||
{
|
||||
log::error!("MCP server error: {}", e);
|
||||
}
|
||||
*mcp_state.mcp_running.write().await = false;
|
||||
});
|
||||
} else if is_running {
|
||||
// Stop MCP server
|
||||
let _ = state.mcp_shutdown_tx.send(true);
|
||||
*state.mcp_running.write().await = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_mcp_status(app: AppHandle) -> TuskResult<McpStatus> {
|
||||
// Read settings from file for enabled/port
|
||||
let settings = {
|
||||
let path = get_settings_path(&app)?;
|
||||
if path.exists() {
|
||||
let data = fs::read_to_string(&path)?;
|
||||
serde_json::from_str::<AppSettings>(&data).unwrap_or_default()
|
||||
} else {
|
||||
AppSettings::default()
|
||||
}
|
||||
};
|
||||
|
||||
// Probe the actual port to determine if MCP is running
|
||||
let running = tokio::time::timeout(
|
||||
std::time::Duration::from_millis(500),
|
||||
tokio::net::TcpStream::connect(format!("127.0.0.1:{}", settings.mcp.port)),
|
||||
)
|
||||
.await
|
||||
.map(|r| r.is_ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(McpStatus {
|
||||
enabled: settings.mcp.enabled,
|
||||
port: settings.mcp.port,
|
||||
running,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user