feat(agent): migrate Oracle to JDBC agent, add install progress bar and toast

- Migrate Oracle from rust-oracle to JDBC agent (ojdbc11)
- Delete oracle_driver.rs (567 lines) and rust-oracle dependency
- Add streaming download progress bar for driver/JRE installation
- Add toast notifications for install/uninstall success/failure
- Add "update available" badge in driver manager
This commit is contained in:
t8y2 committed 2026-05-13 09:20:06 +08:00
1 parent b012ae32f9
commit c50323cd6c
14 files changed
+175 -972

No files matched your search

+51 -10
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use tauri::State;
use tauri::{Emitter, State};
use dbx_core::agent_manager::{AgentDriverInfo, AgentManager, AgentRegistry, InstalledDriver};
use dbx_core::connection::AppState;
@@ -43,23 +43,46 @@ pub async fn list_installed_agents(state: State<'_, Arc<AppState>>) -> Result<Ve
}
#[tauri::command]
pub async fn install_agent(state: State<'_, Arc<AppState>>, db_type: String) -> Result<(), String> {
pub async fn install_agent(
app: tauri::AppHandle,
state: State<'_, Arc<AppState>>,
db_type: String,
) -> Result<(), String> {
let am = &state.agent_manager;
let registry = fetch_registry().await?;
let needs_jre = !am.is_jre_installed();
if !am.is_jre_installed() {
if needs_jre {
let platform = AgentManager::current_platform();
let jre_info =
registry.jre.platforms.get(platform).ok_or_else(|| format!("No JRE available for platform: {platform}"))?;
let jre_archive = am.base_dir().join("jre-download.tar.gz");
download_with_proxy(&jre_info.url, &jre_archive).await?;
let _ = app.emit(
"agent-install-progress",
serde_json::json!({
"step": "jre", "downloaded": 0u64, "total": jre_info.size,
}),
);
download_with_progress(&app, "jre", &jre_info.url, &jre_archive, jre_info.size).await?;
let _ = app.emit(
"agent-install-progress",
serde_json::json!({
"step": "jre-extract", "downloaded": 0u64, "total": 0u64,
}),
);
extract_archive(&jre_archive, &am.base_dir().join("jre"))?;
std::fs::remove_file(&jre_archive).ok();
}
let driver = registry.drivers.get(&db_type).ok_or_else(|| format!("Unknown driver type: {db_type}"))?;
let jar_path = am.driver_jar_path(&db_type);
download_with_proxy(&driver.jar.url, &jar_path).await?;
let _ = app.emit(
"agent-install-progress",
serde_json::json!({
"step": "driver", "downloaded": 0u64, "total": driver.jar.size,
}),
);
download_with_progress(&app, "driver", &driver.jar.url, &jar_path, driver.jar.size).await?;
let mut local_state = am.load_state();
local_state.jre_version = Some(registry.jre.version.clone());
@@ -68,6 +91,7 @@ pub async fn install_agent(state: State<'_, Arc<AppState>>, db_type: String) ->
InstalledDriver { version: driver.version.clone(), installed_at: chrono::Utc::now().to_rfc3339() },
);
am.save_state(&local_state)?;
let _ = app.emit("agent-install-progress", serde_json::json!({ "step": "done" }));
Ok(())
}
@@ -94,7 +118,7 @@ pub async fn check_jre_installed(state: State<'_, Arc<AppState>>) -> Result<bool
}
#[tauri::command]
pub async fn reinstall_jre(state: State<'_, Arc<AppState>>) -> Result<(), String> {
pub async fn reinstall_jre(app: tauri::AppHandle, state: State<'_, Arc<AppState>>) -> Result<(), String> {
let am = &state.agent_manager;
let jre_dir = am.base_dir().join("jre");
if jre_dir.exists() {
@@ -105,12 +129,13 @@ pub async fn reinstall_jre(state: State<'_, Arc<AppState>>) -> Result<(), String
let jre_info =
registry.jre.platforms.get(platform).ok_or_else(|| format!("No JRE available for platform: {platform}"))?;
let jre_archive = am.base_dir().join("jre-download.tar.gz");
download_with_proxy(&jre_info.url, &jre_archive).await?;
download_with_progress(&app, "jre", &jre_info.url, &jre_archive, jre_info.size).await?;
extract_archive(&jre_archive, &jre_dir)?;
std::fs::remove_file(&jre_archive).ok();
let mut local_state = am.load_state();
local_state.jre_version = Some(registry.jre.version.clone());
am.save_state(&local_state)?;
let _ = app.emit("agent-install-progress", serde_json::json!({ "step": "done" }));
Ok(())
}
@@ -140,7 +165,13 @@ async fn fetch_registry() -> Result<AgentRegistry, String> {
Err(format!("Failed to fetch agent registry: {last_err}"))
}
async fn download_with_proxy(url: &str, dest: &std::path::Path) -> Result<(), String> {
async fn download_with_progress(
app: &tauri::AppHandle,
step: &str,
url: &str,
dest: &std::path::Path,
total_size: u64,
) -> Result<(), String> {
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
}
@@ -160,8 +191,18 @@ async fn download_with_proxy(url: &str, dest: &std::path::Path) -> Result<(), St
.and_then(|r| r.error_for_status())
{
Ok(resp) => {
let bytes = resp.bytes().await.map_err(|e| format!("Download read failed: {e}"))?;
std::fs::write(dest, &bytes).map_err(|e| format!("Failed to write file: {e}"))?;
let content_length = resp.content_length().unwrap_or(total_size);
let mut file = std::fs::File::create(dest).map_err(|e| format!("Failed to create file: {e}"))?;
let mut downloaded: u64 = 0;
let mut bytes = resp;
while let Some(chunk) = bytes.chunk().await.map_err(|e| format!("Download stream error: {e}"))? {
std::io::Write::write_all(&mut file, &chunk).map_err(|e| format!("Failed to write chunk: {e}"))?;
downloaded += chunk.len() as u64;
let _ = app.emit(
"agent-install-progress",
serde_json::json!({ "step": step, "downloaded": downloaded, "total": content_length }),
);
}
return Ok(());
}
Err(e) => {
+11 -26
View File
@@ -3,7 +3,7 @@ use tauri::State;
pub use dbx_core::connection::{
connection_url_for_endpoint, expand_tilde, metadata_connection_config, probe_connection_endpoint,
redacted_connection_url_for_endpoint, AppState, MysqlMode, OraclePool, PoolKind,
redacted_connection_url_for_endpoint, AppState, MysqlMode, PoolKind,
};
use dbx_core::db;
use dbx_core::models::connection::{rewrite_jdbc_url_host, ConnectionConfig, DatabaseType};
@@ -97,22 +97,16 @@ pub async fn test_connection(state: State<'_, Arc<AppState>>, config: Connection
.await
.map(|_| "Connection successful".to_string())
}
DatabaseType::Oracle => db::oracle_driver::connect(
&host,
port,
config.database.as_deref().unwrap_or("ORCL"),
&config.username,
&config.password,
config.sysdba,
)
.await
.map(|_| "Connection successful".to_string()),
DatabaseType::Elasticsearch => {
let client =
db::elasticsearch_driver::EsClient::new(&url, Some(&config.username), Some(&config.password));
db::elasticsearch_driver::test_connection(&client).await.map(|_| "Connection successful".to_string())
}
DatabaseType::Dameng | DatabaseType::Kingbase | DatabaseType::Vastbase | DatabaseType::Goldendb => {
DatabaseType::Dameng
| DatabaseType::Kingbase
| DatabaseType::Vastbase
| DatabaseType::Goldendb
| DatabaseType::Oracle => {
state
.agent_manager
.call_daemon::<serde_json::Value>(
@@ -210,25 +204,17 @@ pub async fn connect_db(state: State<'_, Arc<AppState>>, config: ConnectionConfi
.await?;
PoolKind::SqlServer(std::sync::Arc::new(tokio::sync::Mutex::new(client)))
}
DatabaseType::Oracle => {
let client = db::oracle_driver::connect(
&host,
port,
db_config.database.as_deref().unwrap_or("ORCL"),
&db_config.username,
&db_config.password,
db_config.sysdba,
)
.await?;
PoolKind::Oracle(std::sync::Arc::new(OraclePool::new(vec![client])))
}
DatabaseType::Elasticsearch => {
let client =
db::elasticsearch_driver::EsClient::new(&url, Some(&db_config.username), Some(&db_config.password));
db::elasticsearch_driver::test_connection(&client).await?;
PoolKind::Elasticsearch(client)
}
DatabaseType::Dameng | DatabaseType::Kingbase | DatabaseType::Vastbase | DatabaseType::Goldendb => {
DatabaseType::Dameng
| DatabaseType::Kingbase
| DatabaseType::Vastbase
| DatabaseType::Goldendb
| DatabaseType::Oracle => {
let mut client = state.agent_manager.spawn(&db_config.db_type).await?;
client
.call::<serde_json::Value>(
@@ -280,7 +266,6 @@ pub async fn disconnect_db(state: State<'_, Arc<AppState>>, connection_id: Strin
PoolKind::MongoDb(_) => {}
PoolKind::ClickHouse(_) => {}
PoolKind::SqlServer(_) => {}
PoolKind::Oracle(_) => {}
PoolKind::Elasticsearch(_) => {}
PoolKind::Agent(_) => {}
PoolKind::Gaussdb(_) => {}