feat(web): support driver management
This commit is contained in:
parent
af16ac219c
commit
b01c17e709
|
|
@ -1766,9 +1766,11 @@ dependencies = [
|
|||
"argon2",
|
||||
"async-stream",
|
||||
"axum",
|
||||
"chrono",
|
||||
"dbx-core",
|
||||
"futures",
|
||||
"log",
|
||||
"reqwest 0.12.28",
|
||||
"rustls 0.23.40",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -623,14 +623,16 @@ function handleContextMenu(e: MouseEvent) {
|
|||
e.preventDefault();
|
||||
}
|
||||
|
||||
function openDriverStoreFromEvent() {
|
||||
showDriverStore.value = true;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
console.log("[STARTUP] onMounted begin");
|
||||
const mountStart = performance.now();
|
||||
applyTheme();
|
||||
window.addEventListener("keydown", handleKeydown, true);
|
||||
window.addEventListener("dbx-open-driver-store", () => {
|
||||
showDriverStore.value = true;
|
||||
});
|
||||
window.addEventListener("dbx-open-driver-store", openDriverStoreFromEvent);
|
||||
if (isDesktop) {
|
||||
document.addEventListener("contextmenu", handleContextMenu);
|
||||
}
|
||||
|
|
@ -684,6 +686,7 @@ onUnmounted(() => {
|
|||
clearInterval(updateCheckTimer);
|
||||
}
|
||||
window.removeEventListener("keydown", handleKeydown, true);
|
||||
window.removeEventListener("dbx-open-driver-store", openDriverStoreFromEvent);
|
||||
document.removeEventListener("contextmenu", handleContextMenu);
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import type { UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { FolderOpen, Trash2, Download, RotateCcw, Loader2, RefreshCw, Check, Clock3 } from "lucide-vue-next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -17,6 +14,7 @@ import { isTauriRuntime } from "@/lib/tauriRuntime";
|
|||
import { countAvailableAgentDriverUpdates } from "@/lib/agentDriverUpdateBadge";
|
||||
import type { JdbcDriverInfo, JdbcPluginStatus } from "@/types/database";
|
||||
import * as api from "@/lib/api";
|
||||
import type { AgentDriverInfo, JavaRuntimeConfig } from "@/lib/api";
|
||||
import {
|
||||
addDriverInstallQueue,
|
||||
driverInstallProgressPercent,
|
||||
|
|
@ -36,25 +34,6 @@ const emit = defineEmits<{
|
|||
|
||||
// ──────────── Agent drivers ────────────
|
||||
|
||||
interface AgentDriverInfo {
|
||||
db_type: string;
|
||||
label: string;
|
||||
version: string;
|
||||
size: number;
|
||||
installed: boolean;
|
||||
installed_version: string | null;
|
||||
update_available: boolean;
|
||||
jre: string;
|
||||
jre_installed: boolean;
|
||||
}
|
||||
|
||||
type JavaRuntimeMode = "managed" | "system" | "custom";
|
||||
|
||||
interface JavaRuntimeConfig {
|
||||
mode: JavaRuntimeMode;
|
||||
custom_java_path: string | null;
|
||||
}
|
||||
|
||||
const drivers = ref<AgentDriverInfo[]>([]);
|
||||
const installing = ref<string | null>(null);
|
||||
const upgradingAll = ref(false);
|
||||
|
|
@ -69,7 +48,7 @@ const javaRuntimeConfig = ref<JavaRuntimeConfig>({ mode: "managed", custom_java_
|
|||
const customJavaPath = ref("");
|
||||
const savingJavaRuntime = ref(false);
|
||||
|
||||
let unlisten: UnlistenFn | null = null;
|
||||
let unlisten: (() => void) | null = null;
|
||||
|
||||
const installedJres = computed(() => {
|
||||
const jreMap = new Map<string, boolean>();
|
||||
|
|
@ -138,13 +117,13 @@ function removeQueuedDriverInstall(dbType: string) {
|
|||
}
|
||||
|
||||
async function refreshAgents() {
|
||||
updateAgentDrivers(await invoke<AgentDriverInfo[]>("list_installed_agents"));
|
||||
updateAgentDrivers(await api.listInstalledAgents());
|
||||
}
|
||||
|
||||
async function forceRefresh() {
|
||||
refreshing.value = true;
|
||||
try {
|
||||
await invoke("invalidate_agent_registry_cache");
|
||||
await api.invalidateAgentRegistryCache();
|
||||
await refreshAgents();
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
|
|
@ -152,7 +131,7 @@ async function forceRefresh() {
|
|||
}
|
||||
|
||||
async function loadJavaRuntimeConfig() {
|
||||
const config = await invoke<JavaRuntimeConfig>("get_agent_java_runtime_config");
|
||||
const config = await api.getAgentJavaRuntimeConfig();
|
||||
javaRuntimeConfig.value = config;
|
||||
customJavaPath.value = config.custom_java_path ?? "";
|
||||
}
|
||||
|
|
@ -166,11 +145,9 @@ function setJavaRuntimeMode(value: any) {
|
|||
async function saveJavaRuntimeConfig() {
|
||||
savingJavaRuntime.value = true;
|
||||
try {
|
||||
const config = await invoke<JavaRuntimeConfig>("set_agent_java_runtime_config", {
|
||||
config: {
|
||||
mode: javaRuntimeConfig.value.mode,
|
||||
custom_java_path: javaRuntimeConfig.value.mode === "custom" ? customJavaPath.value.trim() || null : null,
|
||||
},
|
||||
const config = await api.setAgentJavaRuntimeConfig({
|
||||
mode: javaRuntimeConfig.value.mode,
|
||||
custom_java_path: javaRuntimeConfig.value.mode === "custom" ? customJavaPath.value.trim() || null : null,
|
||||
});
|
||||
javaRuntimeConfig.value = config;
|
||||
customJavaPath.value = config.custom_java_path ?? "";
|
||||
|
|
@ -208,7 +185,7 @@ async function runDriverInstall(dbType: string) {
|
|||
installing.value = dbType;
|
||||
progress.value = null;
|
||||
try {
|
||||
await invoke("install_agent", { dbType });
|
||||
await api.installAgent(dbType);
|
||||
await refreshAgents();
|
||||
toast(`${label} 驱动安装成功`);
|
||||
} catch (e: any) {
|
||||
|
|
@ -235,7 +212,7 @@ async function upgradeAll() {
|
|||
queuedDriverInstalls.value = [];
|
||||
progress.value = null;
|
||||
try {
|
||||
const count = await invoke<number>("upgrade_all_agents");
|
||||
const count = await api.upgradeAllAgents();
|
||||
await refreshAgents();
|
||||
toast(`${count} 个驱动升级完成`);
|
||||
} catch (e: any) {
|
||||
|
|
@ -252,7 +229,7 @@ async function upgradeAll() {
|
|||
async function uninstallDriver(dbType: string) {
|
||||
const label = drivers.value.find((d) => d.db_type === dbType)?.label ?? dbType;
|
||||
try {
|
||||
await invoke("uninstall_agent", { dbType });
|
||||
await api.uninstallAgent(dbType);
|
||||
await refreshAgents();
|
||||
toast(`${label} 驱动已卸载`);
|
||||
} catch (e: any) {
|
||||
|
|
@ -264,7 +241,7 @@ async function reinstallJre(jreKey: string) {
|
|||
reinstallingJre.value = jreKey;
|
||||
progress.value = null;
|
||||
try {
|
||||
await invoke("reinstall_jre", { jreKey });
|
||||
await api.reinstallJre(jreKey);
|
||||
await refreshAgents();
|
||||
toast(`JRE ${jreKey} 重新安装成功`);
|
||||
} catch (e: any) {
|
||||
|
|
@ -277,7 +254,7 @@ async function reinstallJre(jreKey: string) {
|
|||
|
||||
async function uninstallJre(jreKey: string) {
|
||||
try {
|
||||
await invoke("uninstall_jre", { jreKey });
|
||||
await api.uninstallJre(jreKey);
|
||||
await refreshAgents();
|
||||
toast(`JRE ${jreKey} 已卸载`);
|
||||
} catch (e: any) {
|
||||
|
|
@ -352,7 +329,7 @@ async function installJdbcPluginLocal() {
|
|||
if (typeof selected !== "string") return;
|
||||
isInstallingJdbcPlugin.value = true;
|
||||
try {
|
||||
jdbcPluginStatus.value = await invoke<JdbcPluginStatus>("install_jdbc_plugin_local", { path: selected });
|
||||
jdbcPluginStatus.value = await api.installJdbcPluginLocal(selected);
|
||||
toast(t("settings.jdbcPluginInstallSuccess"));
|
||||
await loadJdbcDrivers();
|
||||
} catch (e: any) {
|
||||
|
|
@ -423,19 +400,18 @@ async function deleteJdbcDriver(path: string) {
|
|||
// ──────────── Lifecycle ────────────
|
||||
|
||||
onMounted(async () => {
|
||||
updateAgentDrivers(await invoke<AgentDriverInfo[]>("list_installed_agents_local"));
|
||||
updateAgentDrivers(await api.listInstalledAgentsLocal());
|
||||
void loadJavaRuntimeConfig();
|
||||
|
||||
invoke<AgentDriverInfo[]>("list_installed_agents").then((result) => {
|
||||
api.listInstalledAgents().then((result) => {
|
||||
updateAgentDrivers(result);
|
||||
});
|
||||
|
||||
unlisten = await listen<DriverInstallProgress>("agent-install-progress", (event) => {
|
||||
const payload = event.payload as any;
|
||||
unlisten = await api.listenAgentInstallProgress((payload) => {
|
||||
if (payload.step === "done" || payload.step === "all-done") {
|
||||
progress.value = null;
|
||||
} else {
|
||||
progress.value = payload;
|
||||
progress.value = payload as DriverInstallProgress;
|
||||
}
|
||||
if (payload.db_type && payload.total_drivers) {
|
||||
upgradingCurrent.value = drivers.value.find((d) => d.db_type === payload.db_type)?.label ?? payload.db_type;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from "vue";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { uuid } from "@/lib/utils";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
|
|
@ -736,10 +735,10 @@ async function loadJdbcDrivers() {
|
|||
}
|
||||
|
||||
async function loadAgentDrivers() {
|
||||
if (!isDesktop) return;
|
||||
try {
|
||||
agentDrivers.value = await invoke<AgentDriverInstallState[]>("list_installed_agents_local");
|
||||
invoke<AgentDriverInstallState[]>("list_installed_agents")
|
||||
agentDrivers.value = await api.listInstalledAgentsLocal();
|
||||
api
|
||||
.listInstalledAgents()
|
||||
.then((drivers) => {
|
||||
agentDrivers.value = drivers;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -42,7 +42,19 @@ export const importJdbcDrivers = forward("importJdbcDrivers");
|
|||
export const deleteJdbcDriver = forward("deleteJdbcDriver");
|
||||
export const jdbcPluginStatus = forward("jdbcPluginStatus");
|
||||
export const installJdbcPlugin = forward("installJdbcPlugin");
|
||||
export const installJdbcPluginLocal = forward("installJdbcPluginLocal");
|
||||
export const uninstallJdbcPlugin = forward("uninstallJdbcPlugin");
|
||||
export const listInstalledAgentsLocal = forward("listInstalledAgentsLocal");
|
||||
export const listInstalledAgents = forward("listInstalledAgents");
|
||||
export const installAgent = forward("installAgent");
|
||||
export const upgradeAllAgents = forward("upgradeAllAgents");
|
||||
export const uninstallAgent = forward("uninstallAgent");
|
||||
export const getAgentJavaRuntimeConfig = forward("getAgentJavaRuntimeConfig");
|
||||
export const setAgentJavaRuntimeConfig = forward("setAgentJavaRuntimeConfig");
|
||||
export const invalidateAgentRegistryCache = forward("invalidateAgentRegistryCache");
|
||||
export const reinstallJre = forward("reinstallJre");
|
||||
export const uninstallJre = forward("uninstallJre");
|
||||
export const listenAgentInstallProgress = forward("listenAgentInstallProgress");
|
||||
export const loadSavedSqlLibrary = forward("loadSavedSqlLibrary");
|
||||
export const saveSavedSqlFolder = forward("saveSavedSqlFolder");
|
||||
export const deleteSavedSqlFolder = forward("deleteSavedSqlFolder");
|
||||
|
|
@ -157,6 +169,10 @@ export type {
|
|||
AiStreamChunk,
|
||||
AiChatMessage,
|
||||
AiConversation,
|
||||
AgentDriverInfo,
|
||||
JavaRuntimeMode,
|
||||
JavaRuntimeConfig,
|
||||
DriverInstallProgress,
|
||||
UpdateInfo,
|
||||
RedisDatabaseInfo,
|
||||
RedisKeyInfo,
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ import type {
|
|||
} from "@/types/database";
|
||||
import type { AiConfig } from "@/stores/settingsStore";
|
||||
import type {
|
||||
AgentDriverInfo,
|
||||
AiCompletionRequest,
|
||||
AiStreamChunk,
|
||||
AiConversation,
|
||||
DriverInstallProgress,
|
||||
JavaRuntimeConfig,
|
||||
UpdateInfo,
|
||||
RedisDatabaseInfo,
|
||||
RedisValue,
|
||||
|
|
@ -125,10 +128,69 @@ export async function installJdbcPlugin(): Promise<JdbcPluginStatus> {
|
|||
return { installed: false, version: null, protocol_version: null, compatible: true, path: "" };
|
||||
}
|
||||
|
||||
export async function installJdbcPluginLocal(_path: string): Promise<JdbcPluginStatus> {
|
||||
return { installed: false, version: null, protocol_version: null, compatible: true, path: "" };
|
||||
}
|
||||
|
||||
export async function uninstallJdbcPlugin(): Promise<JdbcPluginStatus> {
|
||||
return { installed: false, version: null, protocol_version: null, compatible: true, path: "" };
|
||||
}
|
||||
|
||||
export async function listInstalledAgentsLocal(): Promise<AgentDriverInfo[]> {
|
||||
return get("/api/agents/installed-local");
|
||||
}
|
||||
|
||||
export async function listInstalledAgents(): Promise<AgentDriverInfo[]> {
|
||||
return get("/api/agents/installed");
|
||||
}
|
||||
|
||||
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 uninstallAgent(dbType: string): Promise<void> {
|
||||
await post("/api/agents/uninstall", { dbType });
|
||||
}
|
||||
|
||||
export async function getAgentJavaRuntimeConfig(): Promise<JavaRuntimeConfig> {
|
||||
return get("/api/agents/java-runtime");
|
||||
}
|
||||
|
||||
export async function setAgentJavaRuntimeConfig(config: JavaRuntimeConfig): Promise<JavaRuntimeConfig> {
|
||||
return post("/api/agents/java-runtime", { config });
|
||||
}
|
||||
|
||||
export async function invalidateAgentRegistryCache(): Promise<void> {
|
||||
await post("/api/agents/invalidate-registry-cache", {});
|
||||
}
|
||||
|
||||
export async function reinstallJre(jreKey?: string): Promise<void> {
|
||||
await post("/api/agents/reinstall-jre", { jreKey });
|
||||
}
|
||||
|
||||
export async function uninstallJre(jreKey: string): Promise<void> {
|
||||
await post("/api/agents/uninstall-jre", { jreKey });
|
||||
}
|
||||
|
||||
export async function listenAgentInstallProgress(
|
||||
handler: (progress: DriverInstallProgress) => void,
|
||||
): Promise<() => void> {
|
||||
const es = new EventSource("/api/agents/progress/global");
|
||||
es.onmessage = (event) => {
|
||||
try {
|
||||
handler(JSON.parse(event.data));
|
||||
} catch {
|
||||
/* ignore malformed progress events */
|
||||
}
|
||||
};
|
||||
return () => es.close();
|
||||
}
|
||||
|
||||
export async function loadSavedSqlLibrary(): Promise<SavedSqlLibrary> {
|
||||
return get("/api/saved-sql");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,34 @@ import type {
|
|||
} from "@/types/database";
|
||||
import type { AiConfig } from "@/stores/settingsStore";
|
||||
|
||||
export interface AgentDriverInfo {
|
||||
db_type: string;
|
||||
label: string;
|
||||
version: string;
|
||||
size: number;
|
||||
installed: boolean;
|
||||
installed_version: string | null;
|
||||
update_available: boolean;
|
||||
jre: string;
|
||||
jre_installed: boolean;
|
||||
}
|
||||
|
||||
export type JavaRuntimeMode = "managed" | "system" | "custom";
|
||||
|
||||
export interface JavaRuntimeConfig {
|
||||
mode: JavaRuntimeMode;
|
||||
custom_java_path: string | null;
|
||||
}
|
||||
|
||||
export interface DriverInstallProgress {
|
||||
step: string;
|
||||
downloaded?: number;
|
||||
total?: number;
|
||||
db_type?: string;
|
||||
current?: number;
|
||||
total_drivers?: number;
|
||||
}
|
||||
|
||||
export interface AiMessage {
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
|
|
@ -304,10 +332,60 @@ export async function installJdbcPlugin(): Promise<JdbcPluginStatus> {
|
|||
return invoke("install_jdbc_plugin");
|
||||
}
|
||||
|
||||
export async function installJdbcPluginLocal(path: string): Promise<JdbcPluginStatus> {
|
||||
return invoke("install_jdbc_plugin_local", { path });
|
||||
}
|
||||
|
||||
export async function uninstallJdbcPlugin(): Promise<JdbcPluginStatus> {
|
||||
return invoke("uninstall_jdbc_plugin");
|
||||
}
|
||||
|
||||
export async function listInstalledAgentsLocal(): Promise<AgentDriverInfo[]> {
|
||||
return invoke("list_installed_agents_local");
|
||||
}
|
||||
|
||||
export async function listInstalledAgents(): Promise<AgentDriverInfo[]> {
|
||||
return invoke("list_installed_agents");
|
||||
}
|
||||
|
||||
export async function installAgent(dbType: string): Promise<void> {
|
||||
return invoke("install_agent", { dbType });
|
||||
}
|
||||
|
||||
export async function upgradeAllAgents(): Promise<number> {
|
||||
return invoke("upgrade_all_agents");
|
||||
}
|
||||
|
||||
export async function uninstallAgent(dbType: string): Promise<void> {
|
||||
return invoke("uninstall_agent", { dbType });
|
||||
}
|
||||
|
||||
export async function getAgentJavaRuntimeConfig(): Promise<JavaRuntimeConfig> {
|
||||
return invoke("get_agent_java_runtime_config");
|
||||
}
|
||||
|
||||
export async function setAgentJavaRuntimeConfig(config: JavaRuntimeConfig): Promise<JavaRuntimeConfig> {
|
||||
return invoke("set_agent_java_runtime_config", { config });
|
||||
}
|
||||
|
||||
export async function invalidateAgentRegistryCache(): Promise<void> {
|
||||
return invoke("invalidate_agent_registry_cache");
|
||||
}
|
||||
|
||||
export async function reinstallJre(jreKey?: string): Promise<void> {
|
||||
return invoke("reinstall_jre", { jreKey });
|
||||
}
|
||||
|
||||
export async function uninstallJre(jreKey: string): Promise<void> {
|
||||
return invoke("uninstall_jre", { jreKey });
|
||||
}
|
||||
|
||||
export async function listenAgentInstallProgress(
|
||||
handler: (progress: DriverInstallProgress) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
return listen<DriverInstallProgress>("agent-install-progress", (event) => handler(event.payload));
|
||||
}
|
||||
|
||||
export async function loadSavedSqlLibrary(): Promise<SavedSqlLibrary> {
|
||||
return invoke("load_saved_sql_library");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,3 +23,5 @@ async-stream = "0.3"
|
|||
futures = "0.3"
|
||||
rustls = { version = "0.23", features = ["aws-lc-rs"] }
|
||||
tokio-util = "0.7"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] }
|
||||
chrono = "0.4"
|
||||
|
|
|
|||
|
|
@ -82,6 +82,20 @@ async fn main() {
|
|||
.route("/connection/save", post(routes::connection::save_connections))
|
||||
.route("/connection/list", get(routes::connection::load_connections))
|
||||
.route("/plugins", get(routes::plugins::list_plugins))
|
||||
// Agent drivers
|
||||
.route("/agents/installed-local", get(routes::agents::list_installed_agents_local))
|
||||
.route("/agents/installed", get(routes::agents::list_installed_agents))
|
||||
.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/java-runtime",
|
||||
get(routes::agents::get_agent_java_runtime_config).post(routes::agents::set_agent_java_runtime_config),
|
||||
)
|
||||
.route("/agents/invalidate-registry-cache", post(routes::agents::invalidate_agent_registry_cache))
|
||||
.route("/agents/reinstall-jre", post(routes::agents::reinstall_jre))
|
||||
.route("/agents/uninstall-jre", post(routes::agents::uninstall_jre))
|
||||
.route("/agents/progress/{operationId}", get(routes::agents::agent_progress))
|
||||
// Schema
|
||||
.route("/schema/databases", get(routes::schema::list_databases))
|
||||
.route("/schema/schemas", get(routes::schema::list_schemas))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,501 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::{Path, State};
|
||||
use axum::response::sse::{Event, Sse};
|
||||
use axum::Json;
|
||||
use dbx_core::agent_manager::{
|
||||
AgentDriverInfo, AgentManager, AgentRegistry, AgentState, InstalledDriver, JavaRuntimeConfig, JavaRuntimeMode,
|
||||
DEFAULT_JRE_KEY,
|
||||
};
|
||||
use futures::Stream;
|
||||
use serde::Deserialize;
|
||||
use tokio::sync::{broadcast, Mutex};
|
||||
|
||||
use crate::error::AppError;
|
||||
use crate::state::WebState;
|
||||
|
||||
const REGISTRY_PATH: &str = "https://github.com/t8y2/dbx-agents/releases/latest/download/agent-registry.json";
|
||||
const REGISTRY_R2_PATH: &str = "agents/agent-registry.json";
|
||||
|
||||
static REGISTRY_CACHE: std::sync::LazyLock<Mutex<Option<(std::time::Instant, AgentRegistry)>>> =
|
||||
std::sync::LazyLock::new(|| Mutex::new(None));
|
||||
|
||||
const AGENT_TYPES: &[(&str, &str)] = &[
|
||||
("dameng", "达梦 DM8"),
|
||||
("kingbase", "人大金仓 KingbaseES"),
|
||||
("highgo", "瀚高 HighGo"),
|
||||
("vastbase", "Vastbase"),
|
||||
("goldendb", "GoldenDB"),
|
||||
("access", "Microsoft Access"),
|
||||
("oracle", "Oracle"),
|
||||
("oracle-10g", "Oracle 10g"),
|
||||
("h2", "H2"),
|
||||
("snowflake", "Snowflake"),
|
||||
("trino", "Trino (Presto)"),
|
||||
("hive", "Apache Hive"),
|
||||
("db2", "IBM DB2"),
|
||||
("informix", "IBM Informix"),
|
||||
("neo4j", "Neo4j"),
|
||||
("cassandra", "Apache Cassandra"),
|
||||
("bigquery", "Google BigQuery"),
|
||||
("kylin", "Apache Kylin"),
|
||||
("sundb", "SunDB"),
|
||||
("gaussdb", "GaussDB"),
|
||||
("yashandb", "崖山 YashanDB"),
|
||||
("tdengine", "TDengine"),
|
||||
("mongodb", "MongoDB (Legacy)"),
|
||||
];
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AgentTypeRequest {
|
||||
pub db_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JreRequest {
|
||||
pub jre_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JavaRuntimeRequest {
|
||||
pub config: JavaRuntimeConfig,
|
||||
}
|
||||
|
||||
fn build_agent_list(am: &AgentManager, registry: Option<&AgentRegistry>) -> Vec<AgentDriverInfo> {
|
||||
let local_state = am.load_state();
|
||||
AGENT_TYPES
|
||||
.iter()
|
||||
.map(|(key, label)| {
|
||||
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(),
|
||||
version: remote.map(|r| r.version.clone()).unwrap_or_default(),
|
||||
size: remote.map(|r| r.jar.size).unwrap_or(0),
|
||||
installed,
|
||||
installed_version: local.map(|l| l.version.clone()),
|
||||
update_available: match (local, remote) {
|
||||
(Some(l), Some(r)) => l.version != r.version,
|
||||
_ => false,
|
||||
},
|
||||
jre: jre_key.clone(),
|
||||
jre_installed: am.is_jre_installed(&jre_key),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn list_installed_agents_local(
|
||||
State(state): State<Arc<WebState>>,
|
||||
) -> Result<Json<Vec<AgentDriverInfo>>, AppError> {
|
||||
Ok(Json(build_agent_list(&state.app.agent_manager, None)))
|
||||
}
|
||||
|
||||
pub async fn list_installed_agents(State(state): State<Arc<WebState>>) -> Result<Json<Vec<AgentDriverInfo>>, AppError> {
|
||||
let registry = fetch_registry().await.ok();
|
||||
Ok(Json(build_agent_list(&state.app.agent_manager, registry.as_ref())))
|
||||
}
|
||||
|
||||
pub async fn install_agent(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Json(req): Json<AgentTypeRequest>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let tx = progress_sender(&state, "global").await;
|
||||
install_agent_core(&state.app.agent_manager, &req.db_type, &tx, None, None).await.map_err(AppError)?;
|
||||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
pub async fn upgrade_all_agents(State(state): State<Arc<WebState>>) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let tx = progress_sender(&state, "global").await;
|
||||
let am = &state.app.agent_manager;
|
||||
let registry = fetch_registry().await.map_err(AppError)?;
|
||||
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;
|
||||
|
||||
for (index, agent) in updatable.iter().enumerate() {
|
||||
install_agent_from_registry(am, ®istry, &agent.db_type, &tx, Some((index + 1) as u32), Some(total)).await?;
|
||||
}
|
||||
|
||||
send_progress(&tx, serde_json::json!({ "step": "all-done" }));
|
||||
Ok(Json(serde_json::json!({ "count": total })))
|
||||
}
|
||||
|
||||
pub async fn uninstall_agent(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Json(req): Json<AgentTypeRequest>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let am = &state.app.agent_manager;
|
||||
let jar_path = am.driver_jar_path(&req.db_type);
|
||||
if jar_path.exists() {
|
||||
std::fs::remove_file(&jar_path).map_err(|err| AppError(err.to_string()))?;
|
||||
}
|
||||
if let Some(driver_dir) = jar_path.parent() {
|
||||
if driver_dir.exists() {
|
||||
std::fs::remove_dir_all(driver_dir).map_err(|err| AppError(err.to_string()))?;
|
||||
}
|
||||
}
|
||||
let mut local_state = am.load_state();
|
||||
local_state.installed_drivers.remove(&req.db_type);
|
||||
am.save_state(&local_state).map_err(AppError)?;
|
||||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
pub async fn get_agent_java_runtime_config(
|
||||
State(state): State<Arc<WebState>>,
|
||||
) -> Result<Json<JavaRuntimeConfig>, AppError> {
|
||||
Ok(Json(state.app.agent_manager.load_state().java_runtime))
|
||||
}
|
||||
|
||||
pub async fn set_agent_java_runtime_config(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Json(req): Json<JavaRuntimeRequest>,
|
||||
) -> Result<Json<JavaRuntimeConfig>, AppError> {
|
||||
let am = &state.app.agent_manager;
|
||||
let mut config = req.config;
|
||||
if config.mode == JavaRuntimeMode::Custom || config.mode == JavaRuntimeMode::System {
|
||||
let candidate_state = AgentState { java_runtime: config.clone(), ..am.load_state() };
|
||||
let resolved = am.resolve_java_runtime(&candidate_state, DEFAULT_JRE_KEY).map_err(AppError)?;
|
||||
if config.mode == JavaRuntimeMode::Custom {
|
||||
config.custom_java_path = Some(resolved.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
if config.mode != JavaRuntimeMode::Custom {
|
||||
config.custom_java_path = None;
|
||||
}
|
||||
|
||||
let mut local_state = am.load_state();
|
||||
local_state.java_runtime = config.clone();
|
||||
am.save_state(&local_state).map_err(AppError)?;
|
||||
am.stop_daemons().await;
|
||||
Ok(Json(config))
|
||||
}
|
||||
|
||||
pub async fn invalidate_agent_registry_cache() -> Result<Json<serde_json::Value>, AppError> {
|
||||
*REGISTRY_CACHE.lock().await = None;
|
||||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
pub async fn reinstall_jre(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Json(req): Json<JreRequest>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let tx = progress_sender(&state, "global").await;
|
||||
reinstall_jre_core(&state.app.agent_manager, req.jre_key.as_deref().unwrap_or(DEFAULT_JRE_KEY), &tx)
|
||||
.await
|
||||
.map_err(AppError)?;
|
||||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
pub async fn uninstall_jre(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Json(req): Json<JreRequest>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let key = req.jre_key.as_deref().unwrap_or(DEFAULT_JRE_KEY);
|
||||
let am = &state.app.agent_manager;
|
||||
let local_state = am.load_state();
|
||||
let dependents: Vec<&str> =
|
||||
local_state.installed_drivers.iter().filter(|(_, driver)| driver.jre == key).map(|(k, _)| k.as_str()).collect();
|
||||
if !dependents.is_empty() {
|
||||
return Err(AppError(format!("JRE {} 正在被以下驱动使用: {},请先卸载这些驱动", key, dependents.join(", "))));
|
||||
}
|
||||
let jre_dir = am.jre_dir(key);
|
||||
if jre_dir.exists() {
|
||||
std::fs::remove_dir_all(&jre_dir).map_err(|err| AppError(format!("Failed to remove JRE: {err}")))?;
|
||||
}
|
||||
let mut local_state = am.load_state();
|
||||
local_state.jre_versions.remove(key);
|
||||
am.save_state(&local_state).map_err(AppError)?;
|
||||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
pub async fn agent_progress(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Path(operation_id): Path<String>,
|
||||
) -> Result<Sse<impl Stream<Item = Result<Event, std::convert::Infallible>>>, AppError> {
|
||||
let tx = progress_sender(&state, &operation_id).await;
|
||||
Ok(crate::sse::sse_from_channel(tx.subscribe()))
|
||||
}
|
||||
|
||||
async fn progress_sender(state: &WebState, operation_id: &str) -> broadcast::Sender<String> {
|
||||
let mut channels = state.sse_channels.write().await;
|
||||
channels
|
||||
.entry(format!("agent-install-progress:{operation_id}"))
|
||||
.or_insert_with(|| {
|
||||
let (tx, _) = broadcast::channel::<String>(256);
|
||||
tx
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
async fn install_agent_core(
|
||||
am: &AgentManager,
|
||||
db_type: &str,
|
||||
tx: &broadcast::Sender<String>,
|
||||
current: Option<u32>,
|
||||
total_drivers: Option<u32>,
|
||||
) -> Result<(), String> {
|
||||
match fetch_registry().await {
|
||||
Ok(registry) => install_agent_from_registry(am, ®istry, db_type, tx, current, total_drivers).await,
|
||||
Err(registry_err) => {
|
||||
if let Some(local_jar) = find_local_agent_jar(db_type) {
|
||||
install_local_agent(am, db_type, local_jar)?;
|
||||
send_progress(tx, serde_json::json!({ "step": "done" }));
|
||||
return Ok(());
|
||||
}
|
||||
Err(registry_err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn install_agent_from_registry(
|
||||
am: &AgentManager,
|
||||
registry: &AgentRegistry,
|
||||
db_type: &str,
|
||||
tx: &broadcast::Sender<String>,
|
||||
current: Option<u32>,
|
||||
total_drivers: Option<u32>,
|
||||
) -> Result<(), String> {
|
||||
let Some(driver) = registry.drivers.get(db_type) else {
|
||||
if let Some(local_jar) = find_local_agent_jar(db_type) {
|
||||
install_local_agent(am, db_type, local_jar)?;
|
||||
send_progress(tx, serde_json::json!({ "step": "done" }));
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!("Unknown driver type: {db_type}"));
|
||||
};
|
||||
|
||||
let jre_key = &driver.jre;
|
||||
let needs_jre = am.load_state().java_runtime.mode == JavaRuntimeMode::Managed && !am.is_jre_installed(jre_key);
|
||||
if needs_jre {
|
||||
let jre_info =
|
||||
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");
|
||||
send_install_progress(tx, "jre", 0, platform_jre.size, Some(db_type), current, total_drivers);
|
||||
download_with_progress(
|
||||
tx,
|
||||
"jre",
|
||||
&platform_jre.url,
|
||||
&github_url_to_r2_path(&platform_jre.url, "jre"),
|
||||
&jre_archive,
|
||||
platform_jre.size,
|
||||
Some(db_type),
|
||||
current,
|
||||
total_drivers,
|
||||
)
|
||||
.await?;
|
||||
send_install_progress(tx, "jre-extract", 0, 0, Some(db_type), current, total_drivers);
|
||||
extract_archive(&jre_archive, &am.jre_dir(jre_key))?;
|
||||
std::fs::remove_file(&jre_archive).ok();
|
||||
}
|
||||
|
||||
let jar_path = am.driver_jar_path(db_type);
|
||||
send_install_progress(tx, "driver", 0, driver.jar.size, Some(db_type), current, total_drivers);
|
||||
download_with_progress(
|
||||
tx,
|
||||
"driver",
|
||||
&driver.jar.url,
|
||||
&github_url_to_r2_path(&driver.jar.url, "driver"),
|
||||
&jar_path,
|
||||
driver.jar.size,
|
||||
Some(db_type),
|
||||
current,
|
||||
total_drivers,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut local_state = am.load_state();
|
||||
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.to_string(),
|
||||
InstalledDriver {
|
||||
version: driver.version.clone(),
|
||||
installed_at: chrono::Utc::now().to_rfc3339(),
|
||||
jre: jre_key.clone(),
|
||||
},
|
||||
);
|
||||
am.save_state(&local_state)?;
|
||||
send_progress(tx, serde_json::json!({ "step": "done" }));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reinstall_jre_core(am: &AgentManager, jre_key: &str, tx: &broadcast::Sender<String>) -> Result<(), String> {
|
||||
let jre_dir = am.jre_dir(jre_key);
|
||||
if jre_dir.exists() {
|
||||
std::fs::remove_dir_all(&jre_dir).map_err(|err| format!("Failed to remove old JRE: {err}"))?;
|
||||
}
|
||||
let registry = fetch_registry().await?;
|
||||
let jre_info = 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");
|
||||
download_with_progress(
|
||||
tx,
|
||||
"jre",
|
||||
&platform_jre.url,
|
||||
&github_url_to_r2_path(&platform_jre.url, "jre"),
|
||||
&jre_archive,
|
||||
platform_jre.size,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
extract_archive(&jre_archive, &jre_dir)?;
|
||||
std::fs::remove_file(&jre_archive).ok();
|
||||
let mut local_state = am.load_state();
|
||||
local_state.jre_versions.insert(jre_key.to_string(), jre_info.version.clone());
|
||||
am.save_state(&local_state)?;
|
||||
send_progress(tx, serde_json::json!({ "step": "done" }));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn local_agent_jar_candidates(db_type: &str) -> Vec<PathBuf> {
|
||||
let jar_name = format!("dbx-agent-{db_type}.jar");
|
||||
let relative = PathBuf::from("..").join("dbx-agents").join(db_type).join("build").join("libs").join(&jar_name);
|
||||
let nested = PathBuf::from("dbx-agents").join(db_type).join("build").join("libs").join(&jar_name);
|
||||
vec![relative, nested]
|
||||
}
|
||||
|
||||
fn find_local_agent_jar(db_type: &str) -> Option<PathBuf> {
|
||||
local_agent_jar_candidates(db_type).into_iter().find(|path| path.exists())
|
||||
}
|
||||
|
||||
fn install_local_agent(am: &AgentManager, db_type: &str, source: PathBuf) -> Result<(), String> {
|
||||
let jar_path = am.driver_jar_path(db_type);
|
||||
let parent = jar_path.parent().ok_or_else(|| format!("Invalid driver path: {}", jar_path.display()))?;
|
||||
std::fs::create_dir_all(parent).map_err(|err| err.to_string())?;
|
||||
std::fs::copy(&source, &jar_path).map_err(|err| format!("Failed to copy local agent jar: {err}"))?;
|
||||
|
||||
let mut local_state = am.load_state();
|
||||
local_state.installed_drivers.insert(
|
||||
db_type.to_string(),
|
||||
InstalledDriver {
|
||||
version: "0.1.0-local".to_string(),
|
||||
installed_at: chrono::Utc::now().to_rfc3339(),
|
||||
jre: DEFAULT_JRE_KEY.to_string(),
|
||||
},
|
||||
);
|
||||
am.save_state(&local_state)
|
||||
}
|
||||
|
||||
async fn fetch_registry() -> Result<AgentRegistry, String> {
|
||||
{
|
||||
let cache = REGISTRY_CACHE.lock().await;
|
||||
if let Some((ts, registry)) = cache.as_ref() {
|
||||
if ts.elapsed() < std::time::Duration::from_secs(300) {
|
||||
return Ok(registry.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()
|
||||
.map_err(|err| format!("Failed to create HTTP client: {err}"))?;
|
||||
let resp = dbx_core::race_download(&client, REGISTRY_PATH, REGISTRY_R2_PATH, "dbx-agent-manager")
|
||||
.await
|
||||
.map_err(|err| format!("Failed to fetch agent registry: {err}"))?;
|
||||
let registry: AgentRegistry = resp.json().await.map_err(|err| format!("Failed to parse registry: {err}"))?;
|
||||
*REGISTRY_CACHE.lock().await = Some((std::time::Instant::now(), registry.clone()));
|
||||
Ok(registry)
|
||||
}
|
||||
|
||||
fn github_url_to_r2_path(github_url: &str, category: &str) -> String {
|
||||
let filename = github_url.rsplit('/').next().unwrap_or(github_url);
|
||||
match category {
|
||||
"jre" => format!("agents/jre/{filename}"),
|
||||
"driver" => format!("agents/drivers/{filename}"),
|
||||
_ => format!("agents/{filename}"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_with_progress(
|
||||
tx: &broadcast::Sender<String>,
|
||||
step: &str,
|
||||
url: &str,
|
||||
r2_path: &str,
|
||||
dest: &std::path::Path,
|
||||
total_size: u64,
|
||||
db_type: Option<&str>,
|
||||
current: Option<u32>,
|
||||
total_drivers: Option<u32>,
|
||||
) -> Result<(), String> {
|
||||
if let Some(parent) = dest.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|err| err.to_string())?;
|
||||
}
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(300))
|
||||
.build()
|
||||
.map_err(|err| format!("Failed to create HTTP client: {err}"))?;
|
||||
let mut resp = dbx_core::race_download(&client, url, r2_path, "dbx-agent-manager")
|
||||
.await
|
||||
.map_err(|err| format!("Failed to download {url}: {err}"))?;
|
||||
let content_length = resp.content_length().unwrap_or(total_size);
|
||||
let mut file = std::fs::File::create(dest).map_err(|err| format!("Failed to create file: {err}"))?;
|
||||
let mut downloaded = 0;
|
||||
while let Some(chunk) = resp.chunk().await.map_err(|err| format!("Download stream error: {err}"))? {
|
||||
std::io::Write::write_all(&mut file, &chunk).map_err(|err| format!("Failed to write chunk: {err}"))?;
|
||||
downloaded += chunk.len() as u64;
|
||||
send_install_progress(tx, step, downloaded, content_length, db_type, current, total_drivers);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_install_progress(
|
||||
tx: &broadcast::Sender<String>,
|
||||
step: &str,
|
||||
downloaded: u64,
|
||||
total: u64,
|
||||
db_type: Option<&str>,
|
||||
current: Option<u32>,
|
||||
total_drivers: Option<u32>,
|
||||
) {
|
||||
let mut payload = serde_json::json!({ "step": step, "downloaded": downloaded, "total": total });
|
||||
if let Some(value) = db_type {
|
||||
payload["db_type"] = serde_json::json!(value);
|
||||
}
|
||||
if let Some(value) = current {
|
||||
payload["current"] = serde_json::json!(value);
|
||||
}
|
||||
if let Some(value) = total_drivers {
|
||||
payload["total_drivers"] = serde_json::json!(value);
|
||||
}
|
||||
send_progress(tx, payload);
|
||||
}
|
||||
|
||||
fn send_progress(tx: &broadcast::Sender<String>, payload: serde_json::Value) {
|
||||
let _ = tx.send(payload.to_string());
|
||||
}
|
||||
|
||||
fn extract_archive(archive: &std::path::Path, dest: &std::path::Path) -> Result<(), String> {
|
||||
use std::process::Command;
|
||||
std::fs::create_dir_all(dest).map_err(|err| err.to_string())?;
|
||||
let status = Command::new("tar")
|
||||
.args(["xzf", &archive.to_string_lossy(), "-C", &dest.to_string_lossy(), "--strip-components=1"])
|
||||
.status()
|
||||
.map_err(|err| format!("Failed to extract archive: {err}"))?;
|
||||
if !status.success() {
|
||||
return Err("Failed to extract JRE archive".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod agents;
|
||||
pub mod ai;
|
||||
pub mod connection;
|
||||
pub mod database_export;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { strict as assert } from "node:assert";
|
||||
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 webMainSource = readFileSync("crates/dbx-web/src/main.rs", "utf8");
|
||||
const webRoutesSource = readFileSync("crates/dbx-web/src/routes/mod.rs", "utf8");
|
||||
|
||||
const agentFunctions = [
|
||||
"listInstalledAgentsLocal",
|
||||
"listInstalledAgents",
|
||||
"installAgent",
|
||||
"upgradeAllAgents",
|
||||
"uninstallAgent",
|
||||
"getAgentJavaRuntimeConfig",
|
||||
"setAgentJavaRuntimeConfig",
|
||||
"invalidateAgentRegistryCache",
|
||||
"reinstallJre",
|
||||
"uninstallJre",
|
||||
"listenAgentInstallProgress",
|
||||
];
|
||||
|
||||
test("shared frontend API exposes agent driver management functions", () => {
|
||||
for (const name of agentFunctions) {
|
||||
assert.match(apiSource, new RegExp(`export const ${name} = forward\\("${name}"\\)`));
|
||||
assert.match(httpSource, new RegExp(`export async function ${name}\\b`));
|
||||
assert.match(tauriSource, new RegExp(`export async function ${name}\\b`));
|
||||
}
|
||||
});
|
||||
|
||||
test("web backend exposes agent driver management routes", () => {
|
||||
assert.match(webRoutesSource, /pub mod agents;/);
|
||||
assert.match(webMainSource, /\/agents\/installed-local/);
|
||||
assert.match(webMainSource, /\/agents\/install/);
|
||||
assert.match(webMainSource, /\/agents\/progress\/\{operationId\}/);
|
||||
assert.match(webMainSource, /\/agents\/java-runtime/);
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { strict as assert } from "node:assert";
|
||||
import test from "node:test";
|
||||
|
||||
const appSource = readFileSync("apps/desktop/src/App.vue", "utf8");
|
||||
const connectionDialogSource = readFileSync("apps/desktop/src/components/connection/ConnectionDialog.vue", "utf8");
|
||||
const driverStoreSource = readFileSync("apps/desktop/src/components/config/DriverStoreDialog.vue", "utf8");
|
||||
|
||||
test("web runtime handles driver store open events", () => {
|
||||
assert.match(appSource, /showDriverStore\.value = true;/);
|
||||
assert.doesNotMatch(appSource, /if \(!isDesktop\) return;\s+showDriverStore\.value = true;/);
|
||||
});
|
||||
|
||||
test("web runtime can show driver install hints", () => {
|
||||
assert.match(connectionDialogSource, /showAgentDriverInstallHint\(form\.value\.db_type, agentDrivers\.value\)/);
|
||||
assert.doesNotMatch(connectionDialogSource, /isDesktop &&\s+showAgentDriverInstallHint/);
|
||||
});
|
||||
|
||||
test("driver store uses the shared API instead of direct Tauri calls", () => {
|
||||
assert.doesNotMatch(driverStoreSource, /@tauri-apps\/api\/core/);
|
||||
assert.doesNotMatch(driverStoreSource, /@tauri-apps\/api\/event/);
|
||||
assert.match(driverStoreSource, /api\.listInstalledAgents/);
|
||||
assert.match(driverStoreSource, /api\.listenAgentInstallProgress/);
|
||||
});
|
||||
Loading…
Reference in New Issue