fix(web): support offline driver zip import
This commit is contained in:
parent
01572ebad7
commit
73117255c6
|
|
@ -243,15 +243,31 @@ async function uninstallDriver(dbType: string) {
|
|||
|
||||
const importingZip = ref(false);
|
||||
|
||||
async function importOfflineZip() {
|
||||
if (isWeb || importingZip.value) return;
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const selected = await open({
|
||||
title: "选择离线驱动包",
|
||||
multiple: false,
|
||||
filters: [{ name: "ZIP", extensions: ["zip"] }],
|
||||
function chooseWebOfflineZip(): Promise<File | null> {
|
||||
return new Promise((resolve) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".zip";
|
||||
input.onchange = () => resolve(input.files?.[0] ?? null);
|
||||
input.click();
|
||||
});
|
||||
if (typeof selected !== "string") return;
|
||||
}
|
||||
|
||||
async function importOfflineZip() {
|
||||
if (importingZip.value) return;
|
||||
let selected: string | File | null = null;
|
||||
if (isWeb) {
|
||||
selected = await chooseWebOfflineZip();
|
||||
} else {
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const path = await open({
|
||||
title: "选择离线驱动包",
|
||||
multiple: false,
|
||||
filters: [{ name: "ZIP", extensions: ["zip"] }],
|
||||
});
|
||||
selected = typeof path === "string" ? path : null;
|
||||
}
|
||||
if (!selected) return;
|
||||
importingZip.value = true;
|
||||
progress.value = null;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -179,8 +179,16 @@ export async function invalidateAgentRegistryCache(): Promise<void> {
|
|||
await post("/api/agents/invalidate-registry-cache", {});
|
||||
}
|
||||
|
||||
export async function importAgentsFromZip(_path: string): Promise<number> {
|
||||
throw new Error("Offline ZIP import is only available in the desktop app");
|
||||
export async function importAgentsFromZip(fileOrPath: string | File): Promise<number> {
|
||||
if (typeof fileOrPath === "string") {
|
||||
throw new Error("Offline ZIP import in web mode requires a File object, not a file path");
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append("file", fileOrPath);
|
||||
const res = await fetch("/api/agents/import-offline", { method: "POST", body: formData });
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
const result: { count: number } = await res.json();
|
||||
return result.count;
|
||||
}
|
||||
|
||||
export async function importAgentJar(_dbType: string, _path: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -389,7 +389,10 @@ export async function invalidateAgentRegistryCache(): Promise<void> {
|
|||
return invoke("invalidate_agent_registry_cache");
|
||||
}
|
||||
|
||||
export async function importAgentsFromZip(path: string): Promise<number> {
|
||||
export async function importAgentsFromZip(path: string | File): Promise<number> {
|
||||
if (typeof path !== "string") {
|
||||
throw new Error("Desktop offline ZIP import requires a local file path");
|
||||
}
|
||||
return invoke("import_agents_from_zip", { path });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ async fn main() {
|
|||
.route("/agents/install", post(routes::agents::install_agent))
|
||||
.route("/agents/upgrade-all", post(routes::agents::upgrade_all_agents))
|
||||
.route("/agents/uninstall", post(routes::agents::uninstall_agent))
|
||||
.route("/agents/import-offline", post(routes::agents::import_agents_from_zip))
|
||||
.route(
|
||||
"/agents/java-runtime",
|
||||
get(routes::agents::get_agent_java_runtime_config).post(routes::agents::set_agent_java_runtime_config),
|
||||
|
|
@ -199,7 +200,7 @@ async fn main() {
|
|||
// Build app
|
||||
let mut app = Router::new()
|
||||
.nest("/api", api)
|
||||
.layer(DefaultBodyLimit::max(50 * 1024 * 1024))
|
||||
.layer(DefaultBodyLimit::max(300 * 1024 * 1024))
|
||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||
.layer(cors);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::{Path, State};
|
||||
use axum::extract::{Multipart, Path, State};
|
||||
use axum::response::sse::{Event, Sse};
|
||||
use axum::Json;
|
||||
use dbx_core::agent_manager::{
|
||||
|
|
@ -9,7 +9,7 @@ use dbx_core::agent_manager::{
|
|||
};
|
||||
use dbx_core::agent_service::{
|
||||
build_agent_list, download_temp_path, fetch_registry, find_local_agent_jar, github_url_to_r2_path,
|
||||
install_local_agent, invalidate_registry_cache, replace_download,
|
||||
import_offline_zip, install_local_agent, invalidate_registry_cache, replace_download, OfflineImportProgress,
|
||||
};
|
||||
use futures::Stream;
|
||||
use serde::Deserialize;
|
||||
|
|
@ -127,6 +127,48 @@ pub async fn invalidate_agent_registry_cache() -> Result<Json<serde_json::Value>
|
|||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
pub async fn import_agents_from_zip(
|
||||
State(state): State<Arc<WebState>>,
|
||||
mut multipart: Multipart,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let tmp_dir = state.data_dir.join("tmp");
|
||||
std::fs::create_dir_all(&tmp_dir).map_err(|err| AppError(err.to_string()))?;
|
||||
|
||||
while let Some(field) = multipart.next_field().await.map_err(|err| AppError(err.to_string()))? {
|
||||
let file_name = field.file_name().unwrap_or("offline-drivers.zip").to_string();
|
||||
if !file_name.to_ascii_lowercase().ends_with(".zip") {
|
||||
return Err(AppError("Offline driver package must be a .zip file".to_string()));
|
||||
}
|
||||
|
||||
let data = field.bytes().await.map_err(|err| AppError(err.to_string()))?;
|
||||
let zip_path = tmp_dir.join(format!("agent-offline-{}.zip", uuid::Uuid::new_v4()));
|
||||
std::fs::write(&zip_path, &data).map_err(|err| AppError(err.to_string()))?;
|
||||
|
||||
let tx = progress_sender(&state, "global").await;
|
||||
let result = import_offline_zip(&state.app.agent_manager, &zip_path, |p: OfflineImportProgress| {
|
||||
send_progress(
|
||||
&tx,
|
||||
serde_json::json!({
|
||||
"step": p.step,
|
||||
"downloaded": p.current as u64,
|
||||
"total": p.total as u64,
|
||||
"db_type": p.label,
|
||||
"current": p.current,
|
||||
"total_drivers": p.total,
|
||||
}),
|
||||
);
|
||||
})
|
||||
.map_err(AppError);
|
||||
let _ = std::fs::remove_file(&zip_path);
|
||||
|
||||
let result = result?;
|
||||
send_progress(&tx, serde_json::json!({ "step": "done" }));
|
||||
return Ok(Json(serde_json::json!({ "count": result.drivers_installed.len() as u32 })));
|
||||
}
|
||||
|
||||
Err(AppError("No file uploaded".to_string()))
|
||||
}
|
||||
|
||||
pub async fn reinstall_jre(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Json(req): Json<JreRequest>,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import test from "node:test";
|
|||
const apiSource = readFileSync("apps/desktop/src/lib/api.ts", "utf8");
|
||||
const httpSource = readFileSync("apps/desktop/src/lib/http.ts", "utf8");
|
||||
const tauriSource = readFileSync("apps/desktop/src/lib/tauri.ts", "utf8");
|
||||
const driverStoreSource = readFileSync("apps/desktop/src/components/config/DriverStoreDialog.vue", "utf8");
|
||||
const webMainSource = readFileSync("crates/dbx-web/src/main.rs", "utf8");
|
||||
const webRoutesSource = readFileSync("crates/dbx-web/src/routes/mod.rs", "utf8");
|
||||
|
||||
|
|
@ -41,3 +42,14 @@ test("web backend exposes agent driver management routes", () => {
|
|||
assert.match(webMainSource, /\/agents\/java-runtime/);
|
||||
assert.match(webMainSource, /\/ai\/models/);
|
||||
});
|
||||
|
||||
test("web runtime supports importing an offline agent driver zip", () => {
|
||||
assert.match(httpSource, /export async function importAgentsFromZip\(fileOrPath: string \| File\)/);
|
||||
assert.match(httpSource, /FormData/);
|
||||
assert.match(httpSource, /\/api\/agents\/import-offline/);
|
||||
assert.doesNotMatch(httpSource, /Offline ZIP import is only available in the desktop app/);
|
||||
assert.match(webMainSource, /\/agents\/import-offline/);
|
||||
assert.match(driverStoreSource, /chooseWebOfflineZip/);
|
||||
assert.match(driverStoreSource, /accept = "\.zip"/);
|
||||
assert.doesNotMatch(driverStoreSource, /if \(isWeb \|\| importingZip\.value\) return;/);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue