diff --git a/docs/driver-manifest.json b/docs/driver-manifest.json index ae4b5c9..a1c0c7c 100644 --- a/docs/driver-manifest.json +++ b/docs/driver-manifest.json @@ -3,79 +3,79 @@ "drivers": { "mariadb": { "engine": "go", - "version": "go-embedded", + "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/mariadb" }, "diros": { "engine": "go", - "version": "go-embedded", + "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/diros" }, "sphinx": { "engine": "go", - "version": "go-embedded", + "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/sphinx" }, "sqlserver": { "engine": "go", - "version": "go-embedded", + "version": "1.9.6", "checksumPolicy": "off", "downloadUrl": "builtin://activate/sqlserver" }, "sqlite": { "engine": "go", - "version": "go-embedded", + "version": "1.44.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/sqlite" }, "duckdb": { "engine": "go", - "version": "go-embedded", + "version": "2.5.5", "checksumPolicy": "off", "downloadUrl": "builtin://activate/duckdb" }, "dameng": { "engine": "go", - "version": "go-embedded", + "version": "1.8.22", "checksumPolicy": "off", "downloadUrl": "builtin://activate/dameng" }, "kingbase": { "engine": "go", - "version": "go-embedded", + "version": "0.0.0-20201021123113-29bd62a876c3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/kingbase" }, "highgo": { "engine": "go", - "version": "go-embedded", + "version": "0.0.0-local", "checksumPolicy": "off", "downloadUrl": "builtin://activate/highgo" }, "vastbase": { "engine": "go", - "version": "go-embedded", + "version": "1.11.1", "checksumPolicy": "off", "downloadUrl": "builtin://activate/vastbase" }, "mongodb": { "engine": "go", - "version": "go-embedded", + "version": "2.5.0", "checksumPolicy": "off", "downloadUrl": "builtin://activate/mongodb" }, "tdengine": { "engine": "go", - "version": "go-embedded", + "version": "3.7.8", "checksumPolicy": "off", "downloadUrl": "builtin://activate/tdengine" }, "postgres": { "engine": "go", - "version": "go-embedded", + "version": "1.11.1", "checksumPolicy": "off", "downloadUrl": "builtin://activate/postgres" } diff --git a/frontend/src/components/DriverManagerModal.tsx b/frontend/src/components/DriverManagerModal.tsx index a36d412..6bbadd3 100644 --- a/frontend/src/components/DriverManagerModal.tsx +++ b/frontend/src/components/DriverManagerModal.tsx @@ -1,9 +1,11 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Button, Modal, Progress, Space, Table, Tag, Typography, message } from 'antd'; +import { Button, Modal, Progress, Select, Space, Table, Tag, Typography, message } from 'antd'; import { DeleteOutlined, DownloadOutlined, ReloadOutlined } from '@ant-design/icons'; import { EventsOn } from '../../wailsjs/runtime/runtime'; import { DownloadDriverPackage, + GetDriverVersionList, + GetDriverVersionPackageSize, GetDriverStatusList, RemoveDriverPackage, } from '../../wailsjs/go/app/App'; @@ -14,6 +16,8 @@ type DriverStatusRow = { type: string; name: string; builtIn: boolean; + pinnedVersion?: string; + installedVersion?: string; packageSizeText?: string; runtimeAvailable: boolean; packageInstalled: boolean; @@ -35,12 +39,75 @@ type ProgressState = { percent: number; }; +type DriverVersionOption = { + version: string; + downloadUrl: string; + packageSizeText?: string; + recommended?: boolean; + source?: string; + year?: string; + displayLabel?: string; +}; + +const buildVersionOptionKey = (option: DriverVersionOption) => `${option.version}@@${option.downloadUrl}`; +const buildVersionSizeLoadingKey = (driverType: string, optionKey: string) => `${driverType}@@${optionKey}`; + +const buildVersionSelectOptions = (options: DriverVersionOption[]) => { + type SelectOption = { value: string; label: string }; + type SelectGroup = { label: string; options: SelectOption[] }; + + if (options.length === 0) { + return [] as Array; + } + + const yearGroups = new Map(); + const others: SelectOption[] = []; + options.forEach((option) => { + const selectOption: SelectOption = { + value: buildVersionOptionKey(option), + label: option.displayLabel || option.version || '默认版本', + }; + const year = String(option.year || '').trim(); + if (!year) { + others.push(selectOption); + return; + } + const group = yearGroups.get(year) || []; + group.push(selectOption); + yearGroups.set(year, group); + }); + + const sortedYears = Array.from(yearGroups.keys()).sort((a, b) => { + const left = Number.parseInt(a, 10); + const right = Number.parseInt(b, 10); + const leftValid = Number.isFinite(left); + const rightValid = Number.isFinite(right); + if (leftValid && rightValid) { + return right - left; + } + return b.localeCompare(a); + }); + + const grouped: SelectGroup[] = sortedYears.map((year) => ({ + label: `${year} 年`, + options: yearGroups.get(year) || [], + })); + if (others.length > 0) { + grouped.push({ label: '其他', options: others }); + } + return grouped; +}; + const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({ open, onClose }) => { const [loading, setLoading] = useState(false); const [downloadDir, setDownloadDir] = useState(''); const [rows, setRows] = useState([]); const [actionDriver, setActionDriver] = useState(''); const [progressMap, setProgressMap] = useState>({}); + const [versionMap, setVersionMap] = useState>({}); + const [selectedVersionMap, setSelectedVersionMap] = useState>({}); + const [versionLoadingMap, setVersionLoadingMap] = useState>({}); + const [versionSizeLoadingMap, setVersionSizeLoadingMap] = useState>({}); const refreshStatus = useCallback(async (toastOnError = true) => { setLoading(true); @@ -65,6 +132,8 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({ type: String(item.type || '').trim(), name: String(item.name || item.type || '').trim(), builtIn: !!item.builtIn, + pinnedVersion: String(item.pinnedVersion || '').trim() || undefined, + installedVersion: String(item.installedVersion || '').trim() || undefined, packageSizeText: String(item.packageSizeText || '').trim() || undefined, runtimeAvailable: !!item.runtimeAvailable, packageInstalled: !!item.packageInstalled, @@ -82,6 +151,155 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({ } }, [downloadDir]); + const loadVersionOptions = useCallback(async (row: DriverStatusRow, toastOnError = false) => { + if (row.builtIn) { + return [] as DriverVersionOption[]; + } + const driverType = String(row.type || '').trim(); + if (!driverType) { + return [] as DriverVersionOption[]; + } + setVersionLoadingMap((prev) => ({ ...prev, [driverType]: true })); + try { + const res = await GetDriverVersionList(driverType, ''); + if (!res?.success) { + if (toastOnError) { + message.error(res?.message || `${row.name} 版本列表加载失败`); + } + return [] as DriverVersionOption[]; + } + const data = (res?.data || {}) as any; + const rawVersions = Array.isArray(data.versions) ? data.versions : []; + const options: DriverVersionOption[] = rawVersions + .map((item: any) => { + const version = String(item.version || '').trim(); + const downloadUrl = String(item.downloadUrl || '').trim(); + if (!version && !downloadUrl) { + return null; + } + return { + version, + downloadUrl, + packageSizeText: String(item.packageSizeText || '').trim() || undefined, + recommended: !!item.recommended, + source: String(item.source || '').trim() || undefined, + year: String(item.year || '').trim() || undefined, + displayLabel: String(item.displayLabel || '').trim() || undefined, + } as DriverVersionOption; + }) + .filter((item: DriverVersionOption | null): item is DriverVersionOption => !!item); + + if (options.length === 0) { + const fallbackVersion = String(row.pinnedVersion || '').trim(); + const fallbackURL = String(row.defaultDownloadUrl || '').trim(); + if (fallbackVersion || fallbackURL) { + options.push({ + version: fallbackVersion, + downloadUrl: fallbackURL, + recommended: true, + source: 'fallback', + displayLabel: fallbackVersion || '默认版本', + }); + } + } + + setVersionMap((prev) => ({ ...prev, [driverType]: options })); + setSelectedVersionMap((prev) => { + const currentKey = prev[driverType]; + if (currentKey && options.some((option) => buildVersionOptionKey(option) === currentKey)) { + return prev; + } + const preferred = + options.find((option) => option.version === row.installedVersion) || + options.find((option) => option.version === row.pinnedVersion) || + options.find((option) => option.recommended) || + options[0]; + if (!preferred) { + return prev; + } + return { ...prev, [driverType]: buildVersionOptionKey(preferred) }; + }); + return options; + } catch (err: any) { + if (toastOnError) { + message.error(`加载 ${row.name} 版本列表失败:${err?.message || String(err)}`); + } + return [] as DriverVersionOption[]; + } finally { + setVersionLoadingMap((prev) => ({ ...prev, [driverType]: false })); + } + }, []); + + const loadVersionPackageSize = useCallback(async (row: DriverStatusRow, optionKey: string) => { + if (row.builtIn) { + return; + } + const driverType = String(row.type || '').trim(); + if (!driverType || !optionKey) { + return; + } + + const options = versionMap[driverType] || []; + const selectedOption = options.find((item) => buildVersionOptionKey(item) === optionKey); + if (!selectedOption) { + return; + } + if (String(selectedOption.packageSizeText || '').trim()) { + return; + } + + const versionText = String(selectedOption.version || '').trim(); + if (!versionText) { + return; + } + + const loadingKey = buildVersionSizeLoadingKey(driverType, optionKey); + if (versionSizeLoadingMap[loadingKey]) { + return; + } + + setVersionSizeLoadingMap((prev) => ({ ...prev, [loadingKey]: true })); + try { + const res = await GetDriverVersionPackageSize(driverType, versionText); + if (!res?.success) { + return; + } + const data = (res?.data || {}) as any; + const sizeText = String(data.packageSizeText || '').trim(); + if (!sizeText) { + return; + } + + setVersionMap((prev) => { + const current = prev[driverType] || []; + let changed = false; + const next = current.map((item) => { + if (buildVersionOptionKey(item) !== optionKey) { + return item; + } + if (String(item.packageSizeText || '').trim() === sizeText) { + return item; + } + changed = true; + return { ...item, packageSizeText: sizeText }; + }); + if (!changed) { + return prev; + } + return { ...prev, [driverType]: next }; + }); + } finally { + setVersionSizeLoadingMap((prev) => { + if (!prev[loadingKey]) { + return prev; + } + const next = { ...prev }; + delete next[loadingKey]; + return next; + }); + } + }, [versionMap, versionSizeLoadingMap]); + useEffect(() => { if (!open) { return; @@ -129,17 +347,30 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({ }, })); try { - const result = await DownloadDriverPackage(row.type, '', downloadDir); + let options = versionMap[row.type] || []; + if (options.length === 0) { + options = await loadVersionOptions(row, true); + } + const selectedKey = selectedVersionMap[row.type]; + const selectedOption = + options.find((item) => buildVersionOptionKey(item) === selectedKey) || + options.find((item) => item.recommended) || + options[0]; + const selectedVersion = selectedOption?.version || row.pinnedVersion || ''; + const selectedDownloadURL = selectedOption?.downloadUrl || row.defaultDownloadUrl || ''; + + const result = await DownloadDriverPackage(row.type, selectedVersion, selectedDownloadURL, downloadDir); if (!result?.success) { message.error(result?.message || `安装 ${row.name} 失败`); return; } - message.success(`${row.name} 已安装启用`); + const versionTip = selectedVersion ? `(${selectedVersion})` : ''; + message.success(`${row.name}${versionTip} 已安装启用`); refreshStatus(false); } finally { setActionDriver(''); } - }, [downloadDir, refreshStatus]); + }, [downloadDir, loadVersionOptions, refreshStatus, selectedVersionMap, versionMap]); const removeDriver = useCallback(async (row: DriverStatusRow) => { setActionDriver(row.type); @@ -174,7 +405,23 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({ dataIndex: 'packageSizeText', key: 'packageSizeText', width: 120, - render: (_: string | undefined, row: DriverStatusRow) => row.packageSizeText || '-', + render: (_: string | undefined, row: DriverStatusRow) => { + if (row.builtIn) { + return row.packageSizeText || '-'; + } + const options = versionMap[row.type] || []; + const selectedKey = selectedVersionMap[row.type]; + const loadingKey = buildVersionSizeLoadingKey(row.type, selectedKey || ''); + const selectedOption = + options.find((item) => buildVersionOptionKey(item) === selectedKey) || + options.find((item) => item.recommended) || + options[0]; + const anyKnownSize = options.find((item) => String(item.packageSizeText || '').trim())?.packageSizeText; + if (selectedKey && versionSizeLoadingMap[loadingKey]) { + return '计算中...'; + } + return selectedOption?.packageSizeText || anyKnownSize || row.packageSizeText || '-'; + }, }, { title: '状态', @@ -224,6 +471,43 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({ return ; }, }, + { + title: '驱动版本', + key: 'driverVersion', + width: 230, + render: (_: string, row: DriverStatusRow) => { + if (row.builtIn) { + return -; + } + const options = versionMap[row.type] || []; + const selectedKey = selectedVersionMap[row.type]; + const selectOptions = buildVersionSelectOptions(options); + return ( +