fix: split screenshot field from log field in issue templates
render: shell blocks image paste/drag — separate into a plain textarea so users can attach screenshots directly.
This commit is contained in:
parent
20d1fa724d
commit
70becfd74f
|
|
@ -133,10 +133,16 @@ body:
|
|||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 错误日志 / 截图
|
||||
description: 可粘贴终端、DevTools、Tauri/Rust 日志,或直接拖入截图。
|
||||
label: 错误日志
|
||||
description: 粘贴终端、DevTools、Tauri/Rust 日志。
|
||||
render: shell
|
||||
placeholder: "请粘贴错误堆栈、控制台输出或截图。"
|
||||
placeholder: "请粘贴错误堆栈或控制台输出。"
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: 截图
|
||||
description: 可直接粘贴或拖入截图。
|
||||
|
||||
- type: input
|
||||
id: app-version
|
||||
|
|
|
|||
|
|
@ -89,6 +89,12 @@ body:
|
|||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: 截图
|
||||
description: 可直接粘贴或拖入截图。
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
|
|
|
|||
|
|
@ -6,12 +6,34 @@ use tokio::sync::Mutex;
|
|||
use crate::db::agent_driver::AgentDriverClient;
|
||||
use crate::models::connection::DatabaseType;
|
||||
|
||||
pub const DEFAULT_JRE_KEY: &str = "17";
|
||||
|
||||
fn default_jre_key() -> String {
|
||||
DEFAULT_JRE_KEY.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AgentRegistry {
|
||||
pub jre: JreInfo,
|
||||
#[serde(default)]
|
||||
pub jre: Option<JreInfo>,
|
||||
#[serde(default)]
|
||||
pub jres: std::collections::HashMap<String, JreInfo>,
|
||||
pub drivers: std::collections::HashMap<String, DriverInfo>,
|
||||
}
|
||||
|
||||
impl AgentRegistry {
|
||||
pub fn resolve_jre(&self, key: &str) -> Option<&JreInfo> {
|
||||
if !self.jres.is_empty() {
|
||||
return self.jres.get(key);
|
||||
}
|
||||
if key == DEFAULT_JRE_KEY {
|
||||
self.jre.as_ref()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JreInfo {
|
||||
pub version: String,
|
||||
|
|
@ -24,6 +46,8 @@ pub struct DriverInfo {
|
|||
pub label: String,
|
||||
pub min_app_version: String,
|
||||
pub jar: ArtifactInfo,
|
||||
#[serde(default = "default_jre_key")]
|
||||
pub jre: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -38,6 +62,8 @@ pub struct AgentState {
|
|||
#[serde(default)]
|
||||
pub jre_version: Option<String>,
|
||||
#[serde(default)]
|
||||
pub jre_versions: std::collections::HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub installed_drivers: std::collections::HashMap<String, InstalledDriver>,
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +71,8 @@ pub struct AgentState {
|
|||
pub struct InstalledDriver {
|
||||
pub version: String,
|
||||
pub installed_at: String,
|
||||
#[serde(default = "default_jre_key")]
|
||||
pub jre: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -56,6 +84,8 @@ pub struct AgentDriverInfo {
|
|||
pub installed: bool,
|
||||
pub installed_version: Option<String>,
|
||||
pub update_available: bool,
|
||||
pub jre: String,
|
||||
pub jre_installed: bool,
|
||||
}
|
||||
|
||||
pub struct AgentManager {
|
||||
|
|
@ -67,9 +97,19 @@ impl AgentManager {
|
|||
pub fn new() -> Self {
|
||||
let home =
|
||||
std::env::var(if cfg!(windows) { "USERPROFILE" } else { "HOME" }).unwrap_or_else(|_| ".".to_string());
|
||||
Self {
|
||||
let mgr = Self {
|
||||
base_dir: PathBuf::from(home).join(".dbx").join("agents"),
|
||||
daemons: Mutex::new(std::collections::HashMap::new()),
|
||||
};
|
||||
mgr.migrate_legacy_jre();
|
||||
mgr
|
||||
}
|
||||
|
||||
fn migrate_legacy_jre(&self) {
|
||||
let legacy = self.base_dir.join("jre");
|
||||
let versioned = self.jre_dir(DEFAULT_JRE_KEY);
|
||||
if legacy.exists() && !versioned.exists() {
|
||||
let _ = std::fs::rename(&legacy, &versioned);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,11 +117,16 @@ impl AgentManager {
|
|||
&self.base_dir
|
||||
}
|
||||
|
||||
pub fn jre_java_path(&self) -> PathBuf {
|
||||
pub fn jre_dir(&self, jre_key: &str) -> PathBuf {
|
||||
self.base_dir.join(format!("jre-{jre_key}"))
|
||||
}
|
||||
|
||||
pub fn jre_java_path(&self, jre_key: &str) -> PathBuf {
|
||||
let dir = self.jre_dir(jre_key);
|
||||
if cfg!(windows) {
|
||||
self.base_dir.join("jre").join("bin").join("java.exe")
|
||||
dir.join("bin").join("java.exe")
|
||||
} else {
|
||||
self.base_dir.join("jre").join("bin").join("java")
|
||||
dir.join("bin").join("java")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,10 +139,9 @@ impl AgentManager {
|
|||
}
|
||||
|
||||
pub fn load_state(&self) -> AgentState {
|
||||
std::fs::read_to_string(self.state_path())
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or(AgentState { jre_version: None, installed_drivers: Default::default() })
|
||||
std::fs::read_to_string(self.state_path()).ok().and_then(|s| serde_json::from_str(&s).ok()).unwrap_or(
|
||||
AgentState { jre_version: None, jre_versions: Default::default(), installed_drivers: Default::default() },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn save_state(&self, state: &AgentState) -> Result<(), String> {
|
||||
|
|
@ -107,8 +151,8 @@ impl AgentManager {
|
|||
std::fs::write(self.state_path(), json).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn is_jre_installed(&self) -> bool {
|
||||
self.jre_java_path().exists()
|
||||
pub fn is_jre_installed(&self, jre_key: &str) -> bool {
|
||||
self.jre_java_path(jre_key).exists()
|
||||
}
|
||||
|
||||
pub fn is_driver_installed(&self, db_type: &str) -> bool {
|
||||
|
|
@ -146,14 +190,17 @@ impl AgentManager {
|
|||
let key = Self::db_type_to_agent_key(db_type)
|
||||
.ok_or_else(|| format!("{:?} is not an agent-driven database type", db_type))?;
|
||||
|
||||
if !self.is_jre_installed() {
|
||||
return Err("JRE runtime is not installed. Please install it from the Driver Manager.".to_string());
|
||||
let state = self.load_state();
|
||||
let jre_key = state.installed_drivers.get(key).map(|d| d.jre.as_str()).unwrap_or(DEFAULT_JRE_KEY);
|
||||
|
||||
if !self.is_jre_installed(jre_key) {
|
||||
return Err(format!("JRE {jre_key} runtime is not installed. Please install it from the Driver Manager."));
|
||||
}
|
||||
if !self.is_driver_installed(key) {
|
||||
return Err(format!("{key} driver is not installed. Please install it from the Driver Manager."));
|
||||
}
|
||||
|
||||
let java = self.jre_java_path().to_string_lossy().to_string();
|
||||
let java = self.jre_java_path(jre_key).to_string_lossy().to_string();
|
||||
let jar = self.driver_jar_path(key).to_string_lossy().to_string();
|
||||
AgentDriverClient::spawn(&java, &jar).await
|
||||
}
|
||||
|
|
@ -171,13 +218,18 @@ impl AgentManager {
|
|||
let mut daemons = self.daemons.lock().await;
|
||||
|
||||
if !daemons.contains_key(&key) {
|
||||
if !self.is_jre_installed() {
|
||||
return Err("JRE runtime is not installed. Please install it from the Driver Manager.".to_string());
|
||||
let state = self.load_state();
|
||||
let jre_key = state.installed_drivers.get(&key).map(|d| d.jre.as_str()).unwrap_or(DEFAULT_JRE_KEY);
|
||||
|
||||
if !self.is_jre_installed(jre_key) {
|
||||
return Err(format!(
|
||||
"JRE {jre_key} runtime is not installed. Please install it from the Driver Manager."
|
||||
));
|
||||
}
|
||||
if !self.is_driver_installed(&key) {
|
||||
return Err(format!("{key} driver is not installed. Please install it from the Driver Manager."));
|
||||
}
|
||||
let java = self.jre_java_path().to_string_lossy().to_string();
|
||||
let java = self.jre_java_path(jre_key).to_string_lossy().to_string();
|
||||
let jar = self.driver_jar_path(&key).to_string_lossy().to_string();
|
||||
let client = AgentDriverClient::spawn(&java, &jar).await?;
|
||||
daemons.insert(key.clone(), client);
|
||||
|
|
@ -189,7 +241,9 @@ impl AgentManager {
|
|||
Err(e) => {
|
||||
log::warn!("[agent] daemon call failed, respawning: {e}");
|
||||
daemons.remove(&key);
|
||||
let java = self.jre_java_path().to_string_lossy().to_string();
|
||||
let state = self.load_state();
|
||||
let jre_key = state.installed_drivers.get(&key).map(|d| d.jre.as_str()).unwrap_or(DEFAULT_JRE_KEY);
|
||||
let java = self.jre_java_path(jre_key).to_string_lossy().to_string();
|
||||
let jar = self.driver_jar_path(&key).to_string_lossy().to_string();
|
||||
let mut new_client = AgentDriverClient::spawn(&java, &jar).await?;
|
||||
let result = new_client.call::<T>(method, params).await?;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
use tauri::{Emitter, State};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use dbx_core::agent_manager::{AgentDriverInfo, AgentManager, AgentRegistry, InstalledDriver};
|
||||
use dbx_core::agent_manager::{AgentDriverInfo, AgentManager, AgentRegistry, InstalledDriver, DEFAULT_JRE_KEY};
|
||||
use dbx_core::connection::AppState;
|
||||
|
||||
const REGISTRY_PATH: &str = "https://github.com/t8y2/dbx-agents/releases/latest/download/agent-registry.json";
|
||||
|
|
@ -39,6 +39,10 @@ fn build_agent_list(am: &AgentManager, registry: Option<&AgentRegistry>) -> Vec<
|
|||
let installed = am.is_driver_installed(key);
|
||||
let local = local_state.installed_drivers.get(*key);
|
||||
let remote = registry.and_then(|r| r.drivers.get(*key));
|
||||
let jre_key = remote
|
||||
.map(|r| r.jre.clone())
|
||||
.or_else(|| local.map(|l| l.jre.clone()))
|
||||
.unwrap_or_else(|| DEFAULT_JRE_KEY.to_string());
|
||||
AgentDriverInfo {
|
||||
db_type: key.to_string(),
|
||||
label: label.to_string(),
|
||||
|
|
@ -50,6 +54,8 @@ fn build_agent_list(am: &AgentManager, registry: Option<&AgentRegistry>) -> Vec<
|
|||
(Some(l), Some(r)) => l.version != r.version,
|
||||
_ => false,
|
||||
},
|
||||
jre: jre_key.clone(),
|
||||
jre_installed: am.is_jre_installed(&jre_key),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -74,31 +80,37 @@ pub async fn install_agent(
|
|||
) -> Result<(), String> {
|
||||
let am = &state.agent_manager;
|
||||
let registry = fetch_registry().await?;
|
||||
let needs_jre = !am.is_jre_installed();
|
||||
|
||||
let driver = registry.drivers.get(&db_type).ok_or_else(|| format!("Unknown driver type: {db_type}"))?;
|
||||
let jre_key = &driver.jre;
|
||||
let needs_jre = !am.is_jre_installed(jre_key);
|
||||
|
||||
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}"))?;
|
||||
registry.resolve_jre(jre_key).ok_or_else(|| format!("No JRE definition for version: {jre_key}"))?;
|
||||
let platform = AgentManager::current_platform();
|
||||
let platform_jre = jre_info
|
||||
.platforms
|
||||
.get(platform)
|
||||
.ok_or_else(|| format!("No JRE {jre_key} available for platform: {platform}"))?;
|
||||
let jre_archive = am.base_dir().join("jre-download.tar.gz");
|
||||
let _ = app.emit(
|
||||
"agent-install-progress",
|
||||
serde_json::json!({
|
||||
"step": "jre", "downloaded": 0u64, "total": jre_info.size,
|
||||
"step": "jre", "downloaded": 0u64, "total": platform_jre.size,
|
||||
}),
|
||||
);
|
||||
download_with_progress(&app, "jre", &jre_info.url, &jre_archive, jre_info.size).await?;
|
||||
download_with_progress(&app, "jre", &platform_jre.url, &jre_archive, platform_jre.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"))?;
|
||||
extract_archive(&jre_archive, &am.jre_dir(jre_key))?;
|
||||
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);
|
||||
let _ = app.emit(
|
||||
"agent-install-progress",
|
||||
|
|
@ -109,10 +121,16 @@ pub async fn install_agent(
|
|||
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());
|
||||
if let Some(jre_info) = registry.resolve_jre(jre_key) {
|
||||
local_state.jre_versions.insert(jre_key.clone(), jre_info.version.clone());
|
||||
}
|
||||
local_state.installed_drivers.insert(
|
||||
db_type,
|
||||
InstalledDriver { version: driver.version.clone(), installed_at: chrono::Utc::now().to_rfc3339() },
|
||||
InstalledDriver {
|
||||
version: driver.version.clone(),
|
||||
installed_at: chrono::Utc::now().to_rfc3339(),
|
||||
jre: jre_key.clone(),
|
||||
},
|
||||
);
|
||||
am.save_state(&local_state)?;
|
||||
let _ = app.emit("agent-install-progress", serde_json::json!({ "step": "done" }));
|
||||
|
|
@ -137,8 +155,9 @@ pub async fn uninstall_agent(state: State<'_, Arc<AppState>>, db_type: String) -
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_jre_installed(state: State<'_, Arc<AppState>>) -> Result<bool, String> {
|
||||
Ok(state.agent_manager.is_jre_installed())
|
||||
pub async fn check_jre_installed(state: State<'_, Arc<AppState>>, jre_key: Option<String>) -> Result<bool, String> {
|
||||
let key = jre_key.as_deref().unwrap_or(DEFAULT_JRE_KEY);
|
||||
Ok(state.agent_manager.is_jre_installed(key))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
|
@ -148,22 +167,28 @@ pub async fn invalidate_agent_registry_cache() -> Result<(), String> {
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn reinstall_jre(app: tauri::AppHandle, state: State<'_, Arc<AppState>>) -> Result<(), String> {
|
||||
pub async fn reinstall_jre(
|
||||
app: tauri::AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
jre_key: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
let am = &state.agent_manager;
|
||||
let jre_dir = am.base_dir().join("jre");
|
||||
let key = jre_key.as_deref().unwrap_or(DEFAULT_JRE_KEY);
|
||||
let jre_dir = am.jre_dir(key);
|
||||
if jre_dir.exists() {
|
||||
std::fs::remove_dir_all(&jre_dir).map_err(|e| format!("Failed to remove old JRE: {e}"))?;
|
||||
}
|
||||
let registry = fetch_registry().await?;
|
||||
let jre_info = registry.resolve_jre(key).ok_or_else(|| format!("No JRE definition for version: {key}"))?;
|
||||
let platform = AgentManager::current_platform();
|
||||
let jre_info =
|
||||
registry.jre.platforms.get(platform).ok_or_else(|| format!("No JRE available for platform: {platform}"))?;
|
||||
let platform_jre =
|
||||
jre_info.platforms.get(platform).ok_or_else(|| format!("No JRE {key} available for platform: {platform}"))?;
|
||||
let jre_archive = am.base_dir().join("jre-download.tar.gz");
|
||||
download_with_progress(&app, "jre", &jre_info.url, &jre_archive, jre_info.size).await?;
|
||||
download_with_progress(&app, "jre", &platform_jre.url, &jre_archive, platform_jre.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());
|
||||
local_state.jre_versions.insert(key.to_string(), jre_info.version.clone());
|
||||
am.save_state(&local_state)?;
|
||||
let _ = app.emit("agent-install-progress", serde_json::json!({ "step": "done" }));
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Reference in New Issue