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:
1 parent
b012ae32f9
commit
c50323cd6c
14 files changed
+175
-972
No files matched your search
@@ -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) => {
|
||||
|
||||
@@ -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(_) => {}
|
||||
|
||||
Reference in new issue
Block a user