fix(drivers): skip blocked driver updates
This commit is contained in:
parent
381bdfdfd7
commit
7fb53ee0b4
|
|
@ -212,6 +212,11 @@ async function runDriverInstall(dbType: string) {
|
|||
installing.value = dbType;
|
||||
progress.value = null;
|
||||
try {
|
||||
const blockers = await api.checkAgentUpdateBlockers([dbType]);
|
||||
if (blockers.length > 0) {
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") }));
|
||||
return;
|
||||
}
|
||||
await api.installAgent(dbType);
|
||||
await refreshAgents();
|
||||
toast(t("driverStore.driverInstallSuccess", { label }));
|
||||
|
|
@ -239,9 +244,22 @@ async function upgradeAll() {
|
|||
queuedDriverInstalls.value = [];
|
||||
progress.value = null;
|
||||
try {
|
||||
const count = await api.upgradeAllAgents();
|
||||
const updatableDbTypes = drivers.value.filter((driver) => driver.update_available).map((driver) => driver.db_type);
|
||||
const blockers = await api.checkAgentUpdateBlockers(updatableDbTypes);
|
||||
if (blockers.length > 0) {
|
||||
toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") }));
|
||||
return;
|
||||
}
|
||||
const result = await api.upgradeAllAgents();
|
||||
await refreshAgents();
|
||||
toast(t("driverStore.upgradeAllSuccess", { count }));
|
||||
if (result.failed.length > 0) {
|
||||
const failedLabels = result.failed
|
||||
.map((item) => drivers.value.find((driver) => driver.db_type === item.db_type)?.label ?? item.db_type)
|
||||
.join(", ");
|
||||
toast(t("driverStore.upgradeAllPartial", { count: result.upgraded, failed: failedLabels }));
|
||||
} else {
|
||||
toast(t("driverStore.upgradeAllSuccess", { count: result.upgraded }));
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast(t("driverStore.upgradeAllFailed", { error: e }));
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1764,7 +1764,9 @@ export default {
|
|||
chooseJavaExecutable: "Choose Java executable",
|
||||
driverInstallSuccess: "{label} driver installed",
|
||||
driverInstallFailed: "Failed to install {label} driver: {error}",
|
||||
driverUpdateBlocked: "Close these database connections before updating drivers: {labels}",
|
||||
upgradeAllSuccess: "{count} driver(s) upgraded",
|
||||
upgradeAllPartial: "{count} driver(s) upgraded. Skipped: {failed}",
|
||||
upgradeAllFailed: "Batch upgrade failed: {error}",
|
||||
driverUninstallSuccess: "{label} driver uninstalled",
|
||||
driverUninstallFailed: "Failed to uninstall {label} driver: {error}",
|
||||
|
|
|
|||
|
|
@ -1655,7 +1655,9 @@ export default {
|
|||
chooseJavaExecutable: "Seleccionar ejecutable Java",
|
||||
driverInstallSuccess: "Driver {label} instalado",
|
||||
driverInstallFailed: "Error al instalar el driver {label}: {error}",
|
||||
driverUpdateBlocked: "Cierra estas conexiones de base de datos antes de actualizar drivers: {labels}",
|
||||
upgradeAllSuccess: "{count} driver(s) actualizados",
|
||||
upgradeAllPartial: "{count} driver(s) actualizados. Omitidos: {failed}",
|
||||
upgradeAllFailed: "Error al actualizar en lote: {error}",
|
||||
driverUninstallSuccess: "Driver {label} desinstalado",
|
||||
driverUninstallFailed: "Error al desinstalar el driver {label}: {error}",
|
||||
|
|
|
|||
|
|
@ -1725,7 +1725,9 @@ export default {
|
|||
chooseJavaExecutable: "选择 Java 可执行文件",
|
||||
driverInstallSuccess: "{label} 驱动安装成功",
|
||||
driverInstallFailed: "{label} 驱动安装失败: {error}",
|
||||
driverUpdateBlocked: "请先关闭以下数据库连接后再更新驱动: {labels}",
|
||||
upgradeAllSuccess: "{count} 个驱动升级完成",
|
||||
upgradeAllPartial: "{count} 个驱动升级完成,以下驱动已跳过: {failed}",
|
||||
upgradeAllFailed: "批量升级失败: {error}",
|
||||
driverUninstallSuccess: "{label} 驱动已卸载",
|
||||
driverUninstallFailed: "{label} 驱动卸载失败: {error}",
|
||||
|
|
|
|||
|
|
@ -1691,7 +1691,9 @@ export default {
|
|||
chooseJavaExecutable: "選擇 Java 可執行檔",
|
||||
driverInstallSuccess: "{label} 驅動程式安裝成功",
|
||||
driverInstallFailed: "{label} 驅動程式安裝失敗: {error}",
|
||||
driverUpdateBlocked: "請先關閉以下資料庫連線後再更新驅動程式: {labels}",
|
||||
upgradeAllSuccess: "{count} 個驅動程式更新完成",
|
||||
upgradeAllPartial: "{count} 個驅動程式更新完成,以下驅動程式已略過: {failed}",
|
||||
upgradeAllFailed: "批次更新失敗: {error}",
|
||||
driverUninstallSuccess: "{label} 驅動程式已解除安裝",
|
||||
driverUninstallFailed: "{label} 驅動程式解除安裝失敗: {error}",
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export const listInstalledAgents = forward("listInstalledAgents");
|
|||
export const getDriverStoreUsage = forward("getDriverStoreUsage");
|
||||
export const installAgent = forward("installAgent");
|
||||
export const upgradeAllAgents = forward("upgradeAllAgents");
|
||||
export const checkAgentUpdateBlockers = forward("checkAgentUpdateBlockers");
|
||||
export const uninstallAgent = forward("uninstallAgent");
|
||||
export const getAgentJavaRuntimeConfig = forward("getAgentJavaRuntimeConfig");
|
||||
export const setAgentJavaRuntimeConfig = forward("setAgentJavaRuntimeConfig");
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import type {
|
|||
AiConversation,
|
||||
AiModelInfo,
|
||||
DriverStoreUsage,
|
||||
UpgradeAllAgentDriversResult,
|
||||
AgentUpdateBlocker,
|
||||
DesktopSettings,
|
||||
DriverInstallProgress,
|
||||
JavaRuntimeConfig,
|
||||
|
|
@ -242,9 +244,12 @@ export async function installAgent(dbType: string): Promise<void> {
|
|||
await post("/api/agents/install", { dbType });
|
||||
}
|
||||
|
||||
export async function upgradeAllAgents(): Promise<number> {
|
||||
const result: { count: number } = await post("/api/agents/upgrade-all", {});
|
||||
return result.count;
|
||||
export async function upgradeAllAgents(): Promise<UpgradeAllAgentDriversResult> {
|
||||
return post("/api/agents/upgrade-all", {});
|
||||
}
|
||||
|
||||
export async function checkAgentUpdateBlockers(_dbTypes: string[]): Promise<AgentUpdateBlocker[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function uninstallAgent(dbType: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,21 @@ export interface AgentDriverInfo {
|
|||
jre_installed: boolean;
|
||||
}
|
||||
|
||||
export interface AgentDriverUpdateIssue {
|
||||
db_type: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface UpgradeAllAgentDriversResult {
|
||||
upgraded: number;
|
||||
failed: AgentDriverUpdateIssue[];
|
||||
}
|
||||
|
||||
export interface AgentUpdateBlocker {
|
||||
db_type: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type JavaRuntimeMode = "managed" | "system" | "custom";
|
||||
|
||||
export interface JavaRuntimeConfig {
|
||||
|
|
@ -90,6 +105,7 @@ export interface DriverStoreUsage {
|
|||
total_bytes: number;
|
||||
jre_bytes: number;
|
||||
agent_driver_bytes: number;
|
||||
download_cache_bytes?: number;
|
||||
jdbc_plugin_bytes: number;
|
||||
jdbc_driver_bytes: number;
|
||||
jres: DriverStoreUsageItem[];
|
||||
|
|
@ -847,10 +863,14 @@ export async function installAgent(dbType: string): Promise<void> {
|
|||
return invoke("install_agent", { dbType });
|
||||
}
|
||||
|
||||
export async function upgradeAllAgents(): Promise<number> {
|
||||
export async function upgradeAllAgents(): Promise<UpgradeAllAgentDriversResult> {
|
||||
return invoke("upgrade_all_agents");
|
||||
}
|
||||
|
||||
export async function checkAgentUpdateBlockers(dbTypes: string[]): Promise<AgentUpdateBlocker[]> {
|
||||
return invoke("check_agent_update_blockers", { dbTypes });
|
||||
}
|
||||
|
||||
export async function uninstallAgent(dbType: string): Promise<void> {
|
||||
return invoke("uninstall_agent", { dbType });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use crate::db::agent_driver::{AgentDriverClient, AgentMethod};
|
|||
use crate::models::connection::DatabaseType;
|
||||
|
||||
pub const DEFAULT_JRE_KEY: &str = "21";
|
||||
pub const DOWNLOAD_CACHE_DIR_NAME: &str = "download-cache";
|
||||
pub const DOWNLOAD_CACHE_MAX_AGE_DAYS: u64 = 7;
|
||||
|
||||
fn default_jre_key() -> String {
|
||||
DEFAULT_JRE_KEY.to_string()
|
||||
|
|
@ -245,6 +247,8 @@ pub struct DriverStoreUsage {
|
|||
pub total_bytes: u64,
|
||||
pub jre_bytes: u64,
|
||||
pub agent_driver_bytes: u64,
|
||||
#[serde(default)]
|
||||
pub download_cache_bytes: u64,
|
||||
pub jdbc_plugin_bytes: u64,
|
||||
pub jdbc_driver_bytes: u64,
|
||||
pub jres: Vec<DriverStoreUsageItem>,
|
||||
|
|
@ -320,6 +324,14 @@ impl AgentManager {
|
|||
self.base_dir.join("drivers").join(db_type).join("agent.jar")
|
||||
}
|
||||
|
||||
pub fn download_cache_dir(&self) -> PathBuf {
|
||||
self.base_dir.join(DOWNLOAD_CACHE_DIR_NAME)
|
||||
}
|
||||
|
||||
pub fn download_cache_max_age_days(&self) -> u64 {
|
||||
DOWNLOAD_CACHE_MAX_AGE_DAYS
|
||||
}
|
||||
|
||||
fn state_path(&self) -> PathBuf {
|
||||
self.base_dir.join("state.json")
|
||||
}
|
||||
|
|
@ -390,14 +402,17 @@ impl AgentManager {
|
|||
let jdbc_driver_bytes = path_size_bytes(&jdbc_driver_root);
|
||||
let jdbc_total_bytes = path_size_bytes(&jdbc_root);
|
||||
let jdbc_plugin_bytes = jdbc_total_bytes.saturating_sub(jdbc_driver_bytes);
|
||||
let download_cache_bytes = path_size_bytes(&self.download_cache_dir());
|
||||
|
||||
DriverStoreUsage {
|
||||
total_bytes: jre_bytes
|
||||
.saturating_add(agent_driver_bytes)
|
||||
.saturating_add(download_cache_bytes)
|
||||
.saturating_add(jdbc_plugin_bytes)
|
||||
.saturating_add(jdbc_driver_bytes),
|
||||
jre_bytes,
|
||||
agent_driver_bytes,
|
||||
download_cache_bytes,
|
||||
jdbc_plugin_bytes,
|
||||
jdbc_driver_bytes,
|
||||
jres,
|
||||
|
|
@ -440,6 +455,10 @@ impl AgentManager {
|
|||
crate::agent_runtime::stop_daemon_by_key(self, agent_key).await;
|
||||
}
|
||||
|
||||
pub async fn active_daemon_keys(&self) -> Vec<String> {
|
||||
self.daemons.lock().await.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn db_type_to_agent_key(db_type: &DatabaseType, driver_profile: Option<&str>) -> Option<&'static str> {
|
||||
crate::agent_runtime::db_type_to_agent_key(db_type, driver_profile)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
|
@ -27,6 +28,18 @@ pub struct AgentProgressEvent {
|
|||
pub total_drivers: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||
pub struct AgentDriverUpdateIssue {
|
||||
pub db_type: String,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Default)]
|
||||
pub struct UpgradeAllAgentDriversResult {
|
||||
pub upgraded: u32,
|
||||
pub failed: Vec<AgentDriverUpdateIssue>,
|
||||
}
|
||||
|
||||
impl AgentProgressEvent {
|
||||
pub fn step(step: impl Into<String>) -> Self {
|
||||
Self { step: step.into(), downloaded: None, total: None, db_type: None, current: None, total_drivers: None }
|
||||
|
|
@ -163,14 +176,15 @@ pub async fn install_agent_driver(
|
|||
pub async fn upgrade_all_agent_drivers(
|
||||
am: &AgentManager,
|
||||
progress: impl Fn(AgentProgressEvent),
|
||||
) -> Result<u32, String> {
|
||||
) -> Result<UpgradeAllAgentDriversResult, String> {
|
||||
let registry = fetch_registry().await?;
|
||||
let agents = build_agent_list(am, Some(®istry));
|
||||
let updatable: Vec<&AgentDriverInfo> = agents.iter().filter(|agent| agent.update_available).collect();
|
||||
let total = updatable.len() as u32;
|
||||
let mut result = UpgradeAllAgentDriversResult::default();
|
||||
|
||||
for (index, agent) in updatable.iter().enumerate() {
|
||||
install_agent_driver_from_registry(
|
||||
match install_agent_driver_from_registry(
|
||||
am,
|
||||
®istry,
|
||||
&agent.db_type,
|
||||
|
|
@ -178,11 +192,18 @@ pub async fn upgrade_all_agent_drivers(
|
|||
Some((index + 1) as u32),
|
||||
Some(total),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
Ok(()) => result.upgraded += 1,
|
||||
Err(error) => {
|
||||
log::warn!("Failed to update {} agent driver: {}", agent.db_type, error);
|
||||
result.failed.push(AgentDriverUpdateIssue { db_type: agent.db_type.clone(), error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress(AgentProgressEvent::step("all-done"));
|
||||
Ok(total)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn uninstall_agent_driver(am: &AgentManager, db_type: &str) -> Result<(), String> {
|
||||
|
|
@ -238,6 +259,7 @@ pub async fn reinstall_agent_jre(
|
|||
.ok_or_else(|| format!("No JRE {jre_key} available for platform: {platform}"))?;
|
||||
let jre_archive = am.base_dir().join("jre-download.tar.gz");
|
||||
download_with_progress(
|
||||
am,
|
||||
&progress,
|
||||
"jre",
|
||||
&platform_jre.url,
|
||||
|
|
@ -338,6 +360,7 @@ async fn install_agent_driver_from_registry(
|
|||
total_drivers,
|
||||
));
|
||||
download_with_progress(
|
||||
am,
|
||||
progress,
|
||||
"jre",
|
||||
&platform_jre.url,
|
||||
|
|
@ -365,6 +388,7 @@ async fn install_agent_driver_from_registry(
|
|||
total_drivers,
|
||||
));
|
||||
download_with_progress(
|
||||
am,
|
||||
progress,
|
||||
"driver",
|
||||
&driver.jar.url,
|
||||
|
|
@ -396,6 +420,7 @@ async fn install_agent_driver_from_registry(
|
|||
}
|
||||
|
||||
async fn download_with_progress(
|
||||
am: &AgentManager,
|
||||
progress: &impl Fn(AgentProgressEvent),
|
||||
step: &str,
|
||||
url: &str,
|
||||
|
|
@ -410,6 +435,18 @@ async fn download_with_progress(
|
|||
std::fs::create_dir_all(parent).map_err(|err| err.to_string())?;
|
||||
}
|
||||
let tmp = download_temp_path(dest);
|
||||
let cache_path = cached_download_path(am, url, total_size, dest);
|
||||
prune_download_cache(am).ok();
|
||||
if cached_download_is_valid(am, &cache_path, total_size) {
|
||||
std::fs::copy(&cache_path, &tmp).map_err(|err| format!("Failed to copy cached download: {err}"))?;
|
||||
progress(AgentProgressEvent::transfer(step, total_size, total_size).with_batch(
|
||||
db_type,
|
||||
current,
|
||||
total_drivers,
|
||||
));
|
||||
return replace_download(&tmp, dest);
|
||||
}
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(300))
|
||||
.build()
|
||||
|
|
@ -431,9 +468,62 @@ async fn download_with_progress(
|
|||
}
|
||||
std::io::Write::flush(&mut file).map_err(|err| format!("Failed to flush temp file: {err}"))?;
|
||||
drop(file);
|
||||
if let Some(parent) = cache_path.parent() {
|
||||
if let Err(err) = std::fs::create_dir_all(parent) {
|
||||
log::warn!("Failed to create agent download cache directory: {err}");
|
||||
} else if let Err(err) = std::fs::copy(&tmp, &cache_path) {
|
||||
log::warn!("Failed to cache agent download: {err}");
|
||||
}
|
||||
}
|
||||
replace_download(&tmp, dest)
|
||||
}
|
||||
|
||||
fn cached_download_path(am: &AgentManager, url: &str, total_size: u64, dest: &std::path::Path) -> std::path::PathBuf {
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
url.hash(&mut hasher);
|
||||
total_size.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
let file_name = dest.file_name().and_then(|name| name.to_str()).unwrap_or("download");
|
||||
am.download_cache_dir().join(format!("{hash:016x}-{file_name}"))
|
||||
}
|
||||
|
||||
fn cached_download_is_valid(am: &AgentManager, path: &std::path::Path, expected_size: u64) -> bool {
|
||||
let Ok(meta) = std::fs::metadata(path) else {
|
||||
return false;
|
||||
};
|
||||
if !meta.is_file() {
|
||||
return false;
|
||||
}
|
||||
if expected_size > 0 && meta.len() != expected_size {
|
||||
let _ = std::fs::remove_file(path);
|
||||
return false;
|
||||
}
|
||||
let max_age = std::time::Duration::from_secs(am.download_cache_max_age_days() * 24 * 60 * 60);
|
||||
if meta.modified().ok().and_then(|modified| modified.elapsed().ok()).is_some_and(|age| age > max_age) {
|
||||
let _ = std::fs::remove_file(path);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn prune_download_cache(am: &AgentManager) -> Result<(), String> {
|
||||
let cache_dir = am.download_cache_dir();
|
||||
let max_age = std::time::Duration::from_secs(am.download_cache_max_age_days() * 24 * 60 * 60);
|
||||
let Ok(entries) = std::fs::read_dir(&cache_dir) else {
|
||||
return Ok(());
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let Ok(meta) = entry.metadata() else {
|
||||
continue;
|
||||
};
|
||||
if meta.modified().ok().and_then(|modified| modified.elapsed().ok()).is_some_and(|age| age > max_age) {
|
||||
let _ = if meta.is_dir() { std::fs::remove_dir_all(path) } else { std::fs::remove_file(path) };
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn github_url_to_r2_path(github_url: &str, category: &str) -> String {
|
||||
let filename = github_url.rsplit('/').next().unwrap_or(github_url);
|
||||
match category {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
|
@ -650,6 +650,36 @@ impl AppState {
|
|||
Ok(closed)
|
||||
}
|
||||
|
||||
pub async fn active_agent_driver_keys(&self) -> HashSet<String> {
|
||||
let configs = self.configs.read().await;
|
||||
let connections = self.connections.read().await;
|
||||
let mut keys = HashSet::new();
|
||||
|
||||
for (pool_key, pool) in connections.iter() {
|
||||
if !matches!(pool, PoolKind::Agent(_)) {
|
||||
continue;
|
||||
}
|
||||
let Some(config) = config_for_pool_key(pool_key, &configs) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(agent_key) = crate::agent_manager::AgentManager::db_type_to_agent_key(
|
||||
&config.db_type,
|
||||
config.driver_profile.as_deref(),
|
||||
) {
|
||||
keys.insert(agent_key.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
drop(connections);
|
||||
drop(configs);
|
||||
|
||||
for key in self.agent_manager.active_daemon_keys().await {
|
||||
keys.insert(key);
|
||||
}
|
||||
|
||||
keys
|
||||
}
|
||||
|
||||
pub async fn duckdb_existing_pool_is_usable_for_config(&self, config: &ConnectionConfig) -> Result<bool, String> {
|
||||
if config.db_type != DatabaseType::DuckDb {
|
||||
return Ok(false);
|
||||
|
|
@ -800,6 +830,19 @@ fn session_scoped_pool_key(base_pool_key: String, client_session_id: Option<&str
|
|||
.unwrap_or(base_pool_key)
|
||||
}
|
||||
|
||||
fn config_for_pool_key<'a>(
|
||||
pool_key: &str,
|
||||
configs: &'a HashMap<String, ConnectionConfig>,
|
||||
) -> Option<&'a ConnectionConfig> {
|
||||
configs
|
||||
.iter()
|
||||
.filter(|(connection_id, _)| {
|
||||
pool_key.strip_prefix(connection_id.as_str()).is_some_and(|rest| rest.is_empty() || rest.starts_with(':'))
|
||||
})
|
||||
.max_by_key(|(connection_id, _)| connection_id.len())
|
||||
.map(|(_, config)| config)
|
||||
}
|
||||
|
||||
fn session_scoped_pool_key_for(
|
||||
db_type: Option<DatabaseType>,
|
||||
base_pool_key: String,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ pub async fn install_agent(
|
|||
State(state): State<Arc<WebState>>,
|
||||
Json(req): Json<AgentTypeRequest>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
ensure_no_agent_update_blockers(&state.app, std::slice::from_ref(&req.db_type)).await.map_err(AppError)?;
|
||||
let tx = progress_sender(&state, "global").await;
|
||||
install_agent_driver(&state.app.agent_manager, &req.db_type, |event| send_progress_event(&tx, event))
|
||||
.await
|
||||
|
|
@ -63,11 +64,16 @@ pub async fn install_agent(
|
|||
}
|
||||
|
||||
pub async fn upgrade_all_agents(State(state): State<Arc<WebState>>) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let registry = fetch_registry().await.map_err(AppError)?;
|
||||
let agents = build_agent_list(&state.app.agent_manager, Some(®istry));
|
||||
let updatable: Vec<String> =
|
||||
agents.iter().filter(|agent| agent.update_available).map(|agent| agent.db_type.clone()).collect();
|
||||
ensure_no_agent_update_blockers(&state.app, &updatable).await.map_err(AppError)?;
|
||||
let tx = progress_sender(&state, "global").await;
|
||||
let total = upgrade_all_agent_drivers(&state.app.agent_manager, |event| send_progress_event(&tx, event))
|
||||
let result = upgrade_all_agent_drivers(&state.app.agent_manager, |event| send_progress_event(&tx, event))
|
||||
.await
|
||||
.map_err(AppError)?;
|
||||
Ok(Json(serde_json::json!({ "count": total })))
|
||||
Ok(Json(serde_json::to_value(result).map_err(|err| AppError(err.to_string()))?))
|
||||
}
|
||||
|
||||
pub async fn uninstall_agent(
|
||||
|
|
@ -224,3 +230,26 @@ fn send_progress_event(tx: &broadcast::Sender<String>, event: AgentProgressEvent
|
|||
let _ = tx.send(payload);
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_no_agent_update_blockers(
|
||||
state: &dbx_core::connection::AppState,
|
||||
db_types: &[String],
|
||||
) -> Result<(), String> {
|
||||
let candidate_keys: std::collections::HashSet<&str> = db_types.iter().map(String::as_str).collect();
|
||||
if candidate_keys.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut blockers = state
|
||||
.active_agent_driver_keys()
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|key| candidate_keys.contains(key.as_str()))
|
||||
.map(|key| dbx_core::agent_catalog::label_for_key(&key).unwrap_or(&key).to_string())
|
||||
.collect::<Vec<_>>();
|
||||
blockers.sort();
|
||||
if blockers.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("请先关闭以下数据库连接后再更新驱动: {}", blockers.join(", ")))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,16 @@ use dbx_core::agent_manager::{AgentDriverInfo, DriverStoreUsage, JavaRuntimeConf
|
|||
use dbx_core::agent_service::{
|
||||
build_agent_list, fetch_registry, import_agent_jar, import_agents_from_zip as import_agents_from_zip_core,
|
||||
install_agent_driver, invalidate_registry_cache, reinstall_agent_jre, uninstall_agent_driver, uninstall_agent_jre,
|
||||
upgrade_all_agent_drivers, AgentProgressEvent,
|
||||
upgrade_all_agent_drivers, AgentProgressEvent, UpgradeAllAgentDriversResult,
|
||||
};
|
||||
use dbx_core::connection::AppState;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct AgentUpdateBlocker {
|
||||
pub db_type: String,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_installed_agents_local(state: State<'_, Arc<AppState>>) -> Result<Vec<AgentDriverInfo>, String> {
|
||||
Ok(build_agent_list(&state.agent_manager, None))
|
||||
|
|
@ -32,16 +38,33 @@ pub async fn install_agent(
|
|||
state: State<'_, Arc<AppState>>,
|
||||
db_type: String,
|
||||
) -> Result<(), String> {
|
||||
ensure_no_agent_update_blockers(state.inner().as_ref(), std::slice::from_ref(&db_type)).await?;
|
||||
let app_handle = app.clone();
|
||||
install_agent_driver(&state.agent_manager, &db_type, move |event| emit_agent_progress(&app_handle, event)).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn upgrade_all_agents(app: tauri::AppHandle, state: State<'_, Arc<AppState>>) -> Result<u32, String> {
|
||||
pub async fn upgrade_all_agents(
|
||||
app: tauri::AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
) -> Result<UpgradeAllAgentDriversResult, String> {
|
||||
let registry = fetch_registry().await?;
|
||||
let agents = build_agent_list(&state.agent_manager, Some(®istry));
|
||||
let updatable: Vec<String> =
|
||||
agents.iter().filter(|agent| agent.update_available).map(|agent| agent.db_type.clone()).collect();
|
||||
ensure_no_agent_update_blockers(state.inner().as_ref(), &updatable).await?;
|
||||
let app_handle = app.clone();
|
||||
upgrade_all_agent_drivers(&state.agent_manager, move |event| emit_agent_progress(&app_handle, event)).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_agent_update_blockers(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
db_types: Vec<String>,
|
||||
) -> Result<Vec<AgentUpdateBlocker>, String> {
|
||||
Ok(agent_update_blockers(state.inner().as_ref(), &db_types).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn uninstall_agent(state: State<'_, Arc<AppState>>, db_type: String) -> Result<(), String> {
|
||||
uninstall_agent_driver(&state.agent_manager, &db_type).await
|
||||
|
|
@ -131,3 +154,30 @@ pub async fn reinstall_jre(
|
|||
fn emit_agent_progress(app: &tauri::AppHandle, event: AgentProgressEvent) {
|
||||
let _ = app.emit("agent-install-progress", event);
|
||||
}
|
||||
|
||||
async fn ensure_no_agent_update_blockers(state: &AppState, db_types: &[String]) -> Result<(), String> {
|
||||
let blockers = agent_update_blockers(state, db_types).await;
|
||||
if blockers.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let labels = blockers.into_iter().map(|blocker| blocker.label).collect::<Vec<_>>().join(", ");
|
||||
Err(format!("请先关闭以下数据库连接后再更新驱动: {labels}"))
|
||||
}
|
||||
|
||||
async fn agent_update_blockers(state: &AppState, db_types: &[String]) -> Vec<AgentUpdateBlocker> {
|
||||
let candidate_keys: std::collections::HashSet<&str> = db_types.iter().map(String::as_str).collect();
|
||||
if candidate_keys.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let active_keys = state.active_agent_driver_keys().await;
|
||||
let mut blockers = active_keys
|
||||
.into_iter()
|
||||
.filter(|key| candidate_keys.contains(key.as_str()))
|
||||
.map(|db_type| AgentUpdateBlocker {
|
||||
label: dbx_core::agent_catalog::label_for_key(&db_type).unwrap_or(&db_type).to_string(),
|
||||
db_type,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
blockers.sort_by(|left, right| left.label.cmp(&right.label));
|
||||
blockers
|
||||
}
|
||||
|
|
|
|||
|
|
@ -512,6 +512,7 @@ pub fn run() {
|
|||
commands::agents::get_driver_store_usage,
|
||||
commands::agents::install_agent,
|
||||
commands::agents::upgrade_all_agents,
|
||||
commands::agents::check_agent_update_blockers,
|
||||
commands::agents::uninstall_agent,
|
||||
commands::agents::check_jre_installed,
|
||||
commands::agents::get_agent_java_runtime_config,
|
||||
|
|
|
|||
Loading…
Reference in New Issue