mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-12 00:19:40 +08:00
✨ feat(driver-manager): 增强驱动管理网络诊断与本地导入能力
- 新增 CheckDriverNetworkStatus,探测 GitHub API/Release/Go Proxy 可达性并返回代理环境信息。 - 驱动管理弹窗新增网络检测结果、驱动目录复用说明、本地导入入口与日志查看。 - 操作日志支持同签名进度覆盖更新,下载百分比动态刷新,不再重复新增日志行。 - 修正弹窗滚动行为与表格滚动体验。 - refs #128
This commit is contained in:
@@ -1,16 +1,19 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Modal, Progress, Select, Space, Table, Tag, Typography, message } from 'antd';
|
||||
import { DeleteOutlined, DownloadOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { Alert, Button, Collapse, Modal, Progress, Select, Space, Table, Tag, Typography, message } from 'antd';
|
||||
import { DeleteOutlined, DownloadOutlined, FileSearchOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
||||
import {
|
||||
CheckDriverNetworkStatus,
|
||||
DownloadDriverPackage,
|
||||
GetDriverVersionList,
|
||||
GetDriverVersionPackageSize,
|
||||
GetDriverStatusList,
|
||||
InstallLocalDriverPackage,
|
||||
RemoveDriverPackage,
|
||||
SelectDriverPackageFile,
|
||||
} from '../../wailsjs/go/app/App';
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
type DriverStatusRow = {
|
||||
type: string;
|
||||
@@ -23,6 +26,10 @@ type DriverStatusRow = {
|
||||
packageInstalled: boolean;
|
||||
connectable: boolean;
|
||||
defaultDownloadUrl?: string;
|
||||
installDir?: string;
|
||||
packagePath?: string;
|
||||
executablePath?: string;
|
||||
downloadedAt?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
@@ -39,6 +46,32 @@ type ProgressState = {
|
||||
percent: number;
|
||||
};
|
||||
|
||||
type DriverLogEntry = {
|
||||
time: string;
|
||||
text: string;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
type DriverNetworkProbe = {
|
||||
name: string;
|
||||
url: string;
|
||||
reachable: boolean;
|
||||
httpStatus?: number;
|
||||
latencyMs?: number;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type DriverNetworkStatus = {
|
||||
reachable: boolean;
|
||||
summary: string;
|
||||
recommendedProxy: boolean;
|
||||
proxyConfigured: boolean;
|
||||
proxyEnv?: Record<string, string>;
|
||||
checks: DriverNetworkProbe[];
|
||||
checkedAt?: string;
|
||||
logPath?: string;
|
||||
};
|
||||
|
||||
type DriverVersionOption = {
|
||||
version: string;
|
||||
downloadUrl: string;
|
||||
@@ -101,14 +134,65 @@ const buildVersionSelectOptions = (options: DriverVersionOption[]) => {
|
||||
const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({ open, onClose }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [downloadDir, setDownloadDir] = useState('');
|
||||
const [networkChecking, setNetworkChecking] = useState(false);
|
||||
const [networkStatus, setNetworkStatus] = useState<DriverNetworkStatus | null>(null);
|
||||
const [rows, setRows] = useState<DriverStatusRow[]>([]);
|
||||
const [actionDriver, setActionDriver] = useState('');
|
||||
const [progressMap, setProgressMap] = useState<Record<string, ProgressState>>({});
|
||||
const [operationLogMap, setOperationLogMap] = useState<Record<string, DriverLogEntry[]>>({});
|
||||
const [logDriverType, setLogDriverType] = useState('');
|
||||
const [logModalOpen, setLogModalOpen] = useState(false);
|
||||
const [versionMap, setVersionMap] = useState<Record<string, DriverVersionOption[]>>({});
|
||||
const [selectedVersionMap, setSelectedVersionMap] = useState<Record<string, string>>({});
|
||||
const [versionLoadingMap, setVersionLoadingMap] = useState<Record<string, boolean>>({});
|
||||
const [versionSizeLoadingMap, setVersionSizeLoadingMap] = useState<Record<string, boolean>>({});
|
||||
|
||||
const appendOperationLog = useCallback((
|
||||
driverType: string,
|
||||
text: string,
|
||||
signature?: string,
|
||||
mode: 'append' | 'update-last' = 'append',
|
||||
) => {
|
||||
const normalized = String(driverType || '').trim().toLowerCase();
|
||||
const content = String(text || '').trim();
|
||||
if (!normalized || !content) {
|
||||
return;
|
||||
}
|
||||
const sign = String(signature || content).trim() || content;
|
||||
const now = new Date().toLocaleTimeString();
|
||||
setOperationLogMap((prev) => {
|
||||
const history = prev[normalized] || [];
|
||||
if (history.length > 0) {
|
||||
const last = history[history.length - 1];
|
||||
if (last.signature === sign) {
|
||||
if (mode === 'update-last') {
|
||||
if (last.text === content) {
|
||||
return prev;
|
||||
}
|
||||
const nextHistory = [...history];
|
||||
nextHistory[nextHistory.length - 1] = {
|
||||
...last,
|
||||
text: content,
|
||||
time: now,
|
||||
};
|
||||
return { ...prev, [normalized]: nextHistory };
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
const nextHistory = [
|
||||
...history,
|
||||
{
|
||||
time: now,
|
||||
text: content,
|
||||
signature: sign,
|
||||
},
|
||||
];
|
||||
const sliced = nextHistory.length > 200 ? nextHistory.slice(nextHistory.length - 200) : nextHistory;
|
||||
return { ...prev, [normalized]: sliced };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const refreshStatus = useCallback(async (toastOnError = true) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -139,6 +223,10 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
packageInstalled: !!item.packageInstalled,
|
||||
connectable: !!item.connectable,
|
||||
defaultDownloadUrl: String(item.defaultDownloadUrl || '').trim() || undefined,
|
||||
installDir: String(item.installDir || '').trim() || undefined,
|
||||
packagePath: String(item.packagePath || '').trim() || undefined,
|
||||
executablePath: String(item.executablePath || '').trim() || undefined,
|
||||
downloadedAt: String(item.downloadedAt || '').trim() || undefined,
|
||||
message: String(item.message || '').trim() || undefined,
|
||||
}));
|
||||
setRows(nextRows);
|
||||
@@ -151,6 +239,45 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
}
|
||||
}, [downloadDir]);
|
||||
|
||||
const checkNetworkStatus = useCallback(async (toastOnError = false) => {
|
||||
setNetworkChecking(true);
|
||||
try {
|
||||
const res = await CheckDriverNetworkStatus();
|
||||
if (!res?.success) {
|
||||
if (toastOnError) {
|
||||
message.error(res?.message || '驱动网络检测失败');
|
||||
}
|
||||
return;
|
||||
}
|
||||
const data = (res?.data || {}) as any;
|
||||
const checks = Array.isArray(data.checks) ? data.checks : [];
|
||||
const normalizedChecks: DriverNetworkProbe[] = checks.map((item: any) => ({
|
||||
name: String(item.name || '').trim(),
|
||||
url: String(item.url || '').trim(),
|
||||
reachable: !!item.reachable,
|
||||
httpStatus: Number(item.httpStatus || 0) || undefined,
|
||||
latencyMs: Number(item.latencyMs || 0) || undefined,
|
||||
error: String(item.error || '').trim() || undefined,
|
||||
}));
|
||||
setNetworkStatus({
|
||||
reachable: !!data.reachable,
|
||||
summary: String(data.summary || '').trim() || '驱动网络检测已完成',
|
||||
recommendedProxy: !!data.recommendedProxy,
|
||||
proxyConfigured: !!data.proxyConfigured,
|
||||
proxyEnv: (data.proxyEnv || {}) as Record<string, string>,
|
||||
checkedAt: String(data.checkedAt || '').trim() || undefined,
|
||||
checks: normalizedChecks,
|
||||
logPath: String(data.logPath || '').trim() || undefined,
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (toastOnError) {
|
||||
message.error(`驱动网络检测失败:${err?.message || String(err)}`);
|
||||
}
|
||||
} finally {
|
||||
setNetworkChecking(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadVersionOptions = useCallback(async (row: DriverStatusRow, toastOnError = false) => {
|
||||
if (row.builtIn) {
|
||||
return [] as DriverVersionOption[];
|
||||
@@ -305,7 +432,8 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
return;
|
||||
}
|
||||
refreshStatus(false);
|
||||
}, [open, refreshStatus]);
|
||||
checkNetworkStatus(false);
|
||||
}, [checkNetworkStatus, open, refreshStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
@@ -330,11 +458,16 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
percent,
|
||||
},
|
||||
}));
|
||||
const progressText = `${Math.round(percent)}%`;
|
||||
const statusText = String(status || '').toUpperCase();
|
||||
const lineText = `[${statusText}] ${messageText || '-'} (${progressText})`;
|
||||
const lineSignature = `${statusText}|${messageText || '-'}`;
|
||||
appendOperationLog(driverType, lineText, lineSignature, 'update-last');
|
||||
});
|
||||
return () => {
|
||||
off();
|
||||
};
|
||||
}, [open]);
|
||||
}, [appendOperationLog, open]);
|
||||
|
||||
const installDriver = useCallback(async (row: DriverStatusRow) => {
|
||||
setActionDriver(row.type);
|
||||
@@ -346,6 +479,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
percent: 0,
|
||||
},
|
||||
}));
|
||||
appendOperationLog(row.type, '[START] 开始自动安装');
|
||||
try {
|
||||
let options = versionMap[row.type] || [];
|
||||
if (options.length === 0) {
|
||||
@@ -361,25 +495,81 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
|
||||
const result = await DownloadDriverPackage(row.type, selectedVersion, selectedDownloadURL, downloadDir);
|
||||
if (!result?.success) {
|
||||
message.error(result?.message || `安装 ${row.name} 失败`);
|
||||
const errText = result?.message || `安装 ${row.name} 失败`;
|
||||
appendOperationLog(row.type, `[ERROR] ${errText}`);
|
||||
message.error(errText);
|
||||
return;
|
||||
}
|
||||
const versionTip = selectedVersion ? `(${selectedVersion})` : '';
|
||||
appendOperationLog(row.type, `[DONE] 自动安装完成 ${versionTip}`);
|
||||
message.success(`${row.name}${versionTip} 已安装启用`);
|
||||
refreshStatus(false);
|
||||
} finally {
|
||||
setActionDriver('');
|
||||
}
|
||||
}, [downloadDir, loadVersionOptions, refreshStatus, selectedVersionMap, versionMap]);
|
||||
}, [appendOperationLog, downloadDir, loadVersionOptions, refreshStatus, selectedVersionMap, versionMap]);
|
||||
|
||||
const installDriverFromLocalFile = useCallback(async (row: DriverStatusRow) => {
|
||||
const fileRes = await SelectDriverPackageFile(downloadDir);
|
||||
if (!fileRes?.success) {
|
||||
if (String(fileRes?.message || '') !== 'Cancelled') {
|
||||
message.error(fileRes?.message || '选择本地驱动包失败');
|
||||
}
|
||||
return;
|
||||
}
|
||||
const filePath = String((fileRes?.data as any)?.path || '').trim();
|
||||
if (!filePath) {
|
||||
message.error('未选择有效的驱动包文件');
|
||||
return;
|
||||
}
|
||||
|
||||
setActionDriver(row.type);
|
||||
setProgressMap((prev) => ({
|
||||
...prev,
|
||||
[row.type]: {
|
||||
status: 'start',
|
||||
message: '开始导入本地驱动包',
|
||||
percent: 0,
|
||||
},
|
||||
}));
|
||||
appendOperationLog(row.type, `[START] 开始本地导入:${filePath}`);
|
||||
try {
|
||||
const result = await InstallLocalDriverPackage(row.type, filePath, downloadDir);
|
||||
if (!result?.success) {
|
||||
const errText = result?.message || `导入 ${row.name} 本地驱动包失败`;
|
||||
appendOperationLog(row.type, `[ERROR] ${errText}`);
|
||||
message.error(errText);
|
||||
return;
|
||||
}
|
||||
appendOperationLog(row.type, '[DONE] 本地导入安装完成');
|
||||
message.success(`${row.name} 本地驱动包已安装启用`);
|
||||
refreshStatus(false);
|
||||
} finally {
|
||||
setActionDriver('');
|
||||
}
|
||||
}, [appendOperationLog, downloadDir, refreshStatus]);
|
||||
|
||||
const openDriverLog = useCallback((driverType: string) => {
|
||||
const normalized = String(driverType || '').trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return;
|
||||
}
|
||||
setLogDriverType(normalized);
|
||||
setLogModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const removeDriver = useCallback(async (row: DriverStatusRow) => {
|
||||
setActionDriver(row.type);
|
||||
appendOperationLog(row.type, '[START] 开始移除驱动');
|
||||
try {
|
||||
const result = await RemoveDriverPackage(row.type, downloadDir);
|
||||
if (!result?.success) {
|
||||
message.error(result?.message || `移除 ${row.name} 失败`);
|
||||
const errText = result?.message || `移除 ${row.name} 失败`;
|
||||
appendOperationLog(row.type, `[ERROR] ${errText}`);
|
||||
message.error(errText);
|
||||
return;
|
||||
}
|
||||
appendOperationLog(row.type, '[DONE] 驱动移除完成');
|
||||
message.success(`${row.name} 已移除`);
|
||||
setProgressMap((prev) => {
|
||||
const next = { ...prev };
|
||||
@@ -390,7 +580,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
} finally {
|
||||
setActionDriver('');
|
||||
}
|
||||
}, [downloadDir, refreshStatus]);
|
||||
}, [appendOperationLog, downloadDir, refreshStatus]);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
@@ -400,6 +590,25 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
key: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '安装位置',
|
||||
key: 'installPath',
|
||||
width: 260,
|
||||
render: (_: string, row: DriverStatusRow) => {
|
||||
if (row.builtIn) {
|
||||
return <Text type="secondary">内置</Text>;
|
||||
}
|
||||
const installPath = row.executablePath || row.installDir || '-';
|
||||
if (installPath === '-') {
|
||||
return <Text type="secondary">-</Text>;
|
||||
}
|
||||
return (
|
||||
<Text copyable={{ text: installPath }} style={{ fontSize: 12 }}>
|
||||
{installPath}
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '安装包大小',
|
||||
dataIndex: 'packageSizeText',
|
||||
@@ -511,7 +720,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 190,
|
||||
width: 320,
|
||||
render: (_: string, row: DriverStatusRow) => {
|
||||
if (row.builtIn) {
|
||||
return <Text type="secondary">-</Text>;
|
||||
@@ -521,19 +730,20 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
if (isSlimBuildUnavailable && !row.packageInstalled) {
|
||||
return <Text type="secondary">需 Full 版</Text>;
|
||||
}
|
||||
if (row.connectable) {
|
||||
return (
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
loading={loadingAction}
|
||||
onClick={() => removeDriver(row)}
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
const logs = operationLogMap[row.type] || [];
|
||||
const hasLogs = logs.length > 0;
|
||||
|
||||
const mainAction = row.connectable ? (
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
loading={loadingAction}
|
||||
onClick={() => removeDriver(row)}
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
@@ -543,10 +753,41 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
安装启用
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Space size={8} wrap>
|
||||
{mainAction}
|
||||
<Button
|
||||
icon={<FileSearchOutlined />}
|
||||
loading={loadingAction}
|
||||
onClick={() => installDriverFromLocalFile(row)}
|
||||
>
|
||||
本地导入
|
||||
</Button>
|
||||
<Button
|
||||
type={hasLogs ? 'default' : 'text'}
|
||||
disabled={!hasLogs}
|
||||
onClick={() => openDriverLog(row.type)}
|
||||
>
|
||||
日志
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [actionDriver, installDriver, loadVersionOptions, loadVersionPackageSize, progressMap, removeDriver, selectedVersionMap, versionLoadingMap, versionMap, versionSizeLoadingMap]);
|
||||
}, [actionDriver, installDriver, installDriverFromLocalFile, loadVersionOptions, loadVersionPackageSize, openDriverLog, operationLogMap, progressMap, removeDriver, selectedVersionMap, versionLoadingMap, versionMap, versionSizeLoadingMap]);
|
||||
|
||||
const activeLogRow = useMemo(() => {
|
||||
if (!logDriverType) {
|
||||
return undefined;
|
||||
}
|
||||
return rows.find((item) => item.type === logDriverType);
|
||||
}, [logDriverType, rows]);
|
||||
|
||||
const activeDriverLogs = operationLogMap[logDriverType] || [];
|
||||
const activeDriverLogLines = activeDriverLogs.map((item) => `[${item.time}] ${item.text}`);
|
||||
const proxyEnvEntries = Object.entries(networkStatus?.proxyEnv || {});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -554,11 +795,23 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
width={980}
|
||||
style={{ top: 24 }}
|
||||
styles={{
|
||||
body: {
|
||||
maxHeight: 'calc(100vh - 220px)',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
paddingRight: 18,
|
||||
},
|
||||
}}
|
||||
destroyOnClose
|
||||
footer={[
|
||||
<Button key="refresh" icon={<ReloadOutlined />} onClick={() => refreshStatus(true)} loading={loading}>
|
||||
刷新
|
||||
</Button>,
|
||||
<Button key="network" onClick={() => checkNetworkStatus(true)} loading={networkChecking}>
|
||||
网络检测
|
||||
</Button>,
|
||||
<Button key="close" type="primary" onClick={onClose}>
|
||||
关闭
|
||||
</Button>,
|
||||
@@ -566,6 +819,67 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
>
|
||||
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
||||
<Text type="secondary">除 MySQL / Redis / Oracle / PostgreSQL 外,其他数据源需先安装启用后再连接。</Text>
|
||||
{networkStatus ? (
|
||||
<Alert
|
||||
type={networkStatus.reachable ? 'success' : 'warning'}
|
||||
showIcon
|
||||
message={networkStatus.summary}
|
||||
description={(
|
||||
<Space direction="vertical" size={6} style={{ width: '100%' }}>
|
||||
<Text type="secondary">
|
||||
驱动下载依赖 GitHub 与 Go 模块代理网络。若检测失败,建议先启用 HTTP/HTTPS/SOCKS5 代理后重试。
|
||||
</Text>
|
||||
<Collapse
|
||||
size="small"
|
||||
items={[
|
||||
{
|
||||
key: 'checks',
|
||||
label: '查看网络检测明细',
|
||||
children: (
|
||||
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||
{networkStatus.checks.map((item) => (
|
||||
<Text key={`${item.name}-${item.url}`} type={item.reachable ? 'secondary' : 'danger'}>
|
||||
{item.name}:{item.reachable ? '可达' : '不可达'}{item.httpStatus ? `,HTTP ${item.httpStatus}` : ''}{item.latencyMs ? `,${item.latencyMs}ms` : ''}{item.error ? `,${item.error}` : ''}
|
||||
</Text>
|
||||
))}
|
||||
{proxyEnvEntries.length > 0 ? (
|
||||
<Text type="secondary">
|
||||
检测到代理环境变量:{proxyEnvEntries.map(([key]) => key).join('、')}
|
||||
</Text>
|
||||
) : (
|
||||
<Text type="secondary">未检测到系统代理环境变量。</Text>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Alert type="info" showIcon message={networkChecking ? '正在检测驱动下载网络...' : '尚未完成网络检测'} />
|
||||
)}
|
||||
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="驱动目录与复用说明"
|
||||
description={(
|
||||
<Space direction="vertical" size={6} style={{ width: '100%' }}>
|
||||
<Text type="secondary">自动下载和手动导入的驱动都会落盘到以下目录;后续版本升级可重复复用已下载驱动。</Text>
|
||||
<Text type="secondary">手动导入支持单个驱动代理文件(如 `mariadb-driver-agent` / `mariadb-driver-agent.exe`)或驱动总包 `GoNavi-DriverAgents.zip`。</Text>
|
||||
<Paragraph copyable={{ text: downloadDir || '-' }} style={{ marginBottom: 0 }}>
|
||||
驱动根目录:{downloadDir || '-'}
|
||||
</Paragraph>
|
||||
{networkStatus?.logPath ? (
|
||||
<Paragraph copyable={{ text: networkStatus.logPath }} style={{ marginBottom: 0 }}>
|
||||
运行日志文件:{networkStatus.logPath}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
</Space>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Table
|
||||
rowKey="type"
|
||||
@@ -574,8 +888,40 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
|
||||
dataSource={rows}
|
||||
pagination={false}
|
||||
size="middle"
|
||||
scroll={{ x: 1450 }}
|
||||
/>
|
||||
</Space>
|
||||
<Modal
|
||||
title={`驱动日志 - ${activeLogRow?.name || logDriverType}`}
|
||||
open={logModalOpen}
|
||||
onCancel={() => setLogModalOpen(false)}
|
||||
footer={[
|
||||
<Button key="close-log" type="primary" onClick={() => setLogModalOpen(false)}>
|
||||
关闭
|
||||
</Button>,
|
||||
]}
|
||||
width={780}
|
||||
>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
{activeLogRow?.installDir ? (
|
||||
<Paragraph copyable={{ text: activeLogRow.installDir }} style={{ marginBottom: 0 }}>
|
||||
安装目录:{activeLogRow.installDir}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{activeLogRow?.executablePath ? (
|
||||
<Paragraph copyable={{ text: activeLogRow.executablePath }} style={{ marginBottom: 0 }}>
|
||||
驱动可执行文件:{activeLogRow.executablePath}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{activeDriverLogLines.length > 0 ? (
|
||||
<pre style={{ margin: 0, maxHeight: 360, overflow: 'auto', padding: 12, background: '#fafafa', borderRadius: 8, border: '1px solid #f0f0f0', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
||||
{activeDriverLogLines.join('\n')}
|
||||
</pre>
|
||||
) : (
|
||||
<Text type="secondary">当前驱动暂无操作日志。</Text>
|
||||
)}
|
||||
</Space>
|
||||
</Modal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
2
frontend/wailsjs/go/app/App.d.ts
vendored
2
frontend/wailsjs/go/app/App.d.ts
vendored
@@ -6,6 +6,8 @@ import {redis} from '../models';
|
||||
|
||||
export function ApplyChanges(arg1:connection.ConnectionConfig,arg2:string,arg3:string,arg4:connection.ChangeSet):Promise<connection.QueryResult>;
|
||||
|
||||
export function CheckDriverNetworkStatus():Promise<connection.QueryResult>;
|
||||
|
||||
export function CheckForUpdates():Promise<connection.QueryResult>;
|
||||
|
||||
export function ConfigureDriverRuntimeDirectory(arg1:string):Promise<connection.QueryResult>;
|
||||
|
||||
@@ -6,6 +6,10 @@ export function ApplyChanges(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['app']['App']['ApplyChanges'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function CheckDriverNetworkStatus() {
|
||||
return window['go']['app']['App']['CheckDriverNetworkStatus']();
|
||||
}
|
||||
|
||||
export function CheckForUpdates() {
|
||||
return window['go']['app']['App']['CheckForUpdates']();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
|
||||
"GoNavi-Wails/internal/connection"
|
||||
"GoNavi-Wails/internal/db"
|
||||
"GoNavi-Wails/internal/logger"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"golang.org/x/mod/semver"
|
||||
@@ -79,6 +81,15 @@ type driverDownloadProgressPayload struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type driverNetworkProbeItem struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Reachable bool `json:"reachable"`
|
||||
HTTPStatus int `json:"httpStatus,omitempty"`
|
||||
LatencyMs int64 `json:"latencyMs,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type pinnedDriverPackage struct {
|
||||
Version string
|
||||
DownloadURL string
|
||||
@@ -188,6 +199,7 @@ const (
|
||||
driverVersionWarmupMinInterval = 30 * time.Second
|
||||
driverBundleIndexMaxSize = 1 << 20
|
||||
driverManifestMaxSize = 2 << 20
|
||||
driverNetworkProbeTimeout = 4 * time.Second
|
||||
driverChecksumPolicyStrict = "strict"
|
||||
driverChecksumPolicyWarn = "warn"
|
||||
driverChecksumPolicyOff = "off"
|
||||
@@ -592,6 +604,59 @@ func (a *App) GetDriverStatusList(downloadDir string, manifestURL string) connec
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) CheckDriverNetworkStatus() connection.QueryResult {
|
||||
checks := []driverNetworkProbeItem{
|
||||
{
|
||||
Name: "GitHub API",
|
||||
URL: "https://api.github.com/rate_limit",
|
||||
},
|
||||
{
|
||||
Name: "GitHub 驱动发布",
|
||||
URL: fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", updateRepo, optionalDriverBundleAssetName),
|
||||
},
|
||||
{
|
||||
Name: "Go 模块代理",
|
||||
URL: "https://proxy.golang.org/github.com/go-sql-driver/mysql/@v/list",
|
||||
},
|
||||
}
|
||||
|
||||
allReachable := true
|
||||
for index := range checks {
|
||||
checks[index] = probeDriverNetworkEndpoint(checks[index])
|
||||
if !checks[index].Reachable {
|
||||
allReachable = false
|
||||
}
|
||||
}
|
||||
|
||||
proxyEnv := collectDriverProxyEnv()
|
||||
proxyConfigured := len(proxyEnv) > 0
|
||||
summary := "驱动下载网络检测通过,可直接安装驱动。"
|
||||
if !allReachable {
|
||||
if proxyConfigured {
|
||||
summary = "检测到部分驱动下载地址不可达,请确认系统代理配置有效后重试。"
|
||||
} else {
|
||||
summary = "检测到部分驱动下载地址不可达,建议先配置 HTTP/HTTPS/SOCKS5 代理后再安装驱动。"
|
||||
}
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"reachable": allReachable,
|
||||
"summary": summary,
|
||||
"recommendedProxy": !allReachable,
|
||||
"proxyConfigured": proxyConfigured,
|
||||
"proxyEnv": proxyEnv,
|
||||
"checkedAt": time.Now().Format(time.RFC3339),
|
||||
"checks": checks,
|
||||
}
|
||||
if logPath := strings.TrimSpace(logger.Path()); logPath != "" {
|
||||
data["logPath"] = logPath
|
||||
}
|
||||
return connection.QueryResult{
|
||||
Success: true,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) InstallLocalDriverPackage(driverType string, filePath string, downloadDir string) connection.QueryResult {
|
||||
definition, ok := resolveDriverDefinition(driverType)
|
||||
if !ok {
|
||||
@@ -614,28 +679,27 @@ func (a *App) InstallLocalDriverPackage(driverType string, filePath string, down
|
||||
}
|
||||
db.SetExternalDriverDownloadDirectory(resolvedDir)
|
||||
|
||||
hash := ""
|
||||
if pathText := strings.TrimSpace(filePath); pathText != "" {
|
||||
if fileHash, hashErr := hashFileSHA256(pathText); hashErr == nil {
|
||||
hash = fileHash
|
||||
a.emitDriverDownloadProgress(definition.Type, "start", 0, 100, "开始安装本地驱动包")
|
||||
selectedVersion := resolveDriverInstallVersion(definition.PinnedVersion, "local://manual", definition)
|
||||
meta, installErr := installOptionalDriverAgentFromLocalFile(definition, filePath, resolvedDir, selectedVersion)
|
||||
if installErr != nil {
|
||||
errText := normalizeErrorMessage(installErr)
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, errText)
|
||||
return connection.QueryResult{
|
||||
Success: false,
|
||||
Message: logDriverOperationError(installErr, "导入本地驱动包失败,driver=%s file=%s", definition.Type, strings.TrimSpace(filePath)),
|
||||
}
|
||||
}
|
||||
|
||||
a.emitDriverDownloadProgress(definition.Type, "start", 0, 0, "开始安装")
|
||||
meta := installedDriverPackage{
|
||||
DriverType: definition.Type,
|
||||
Version: resolveDriverInstallVersion(definition.PinnedVersion, "local://activate", definition),
|
||||
FilePath: "",
|
||||
FileName: "embedded-go-driver",
|
||||
DownloadURL: "local://activate",
|
||||
SHA256: hash,
|
||||
DownloadedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
a.emitDriverDownloadProgress(definition.Type, "downloading", 90, 100, "写入驱动元数据")
|
||||
if err := writeInstalledDriverPackage(resolvedDir, definition.Type, meta); err != nil {
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, err.Error())
|
||||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||||
errText := normalizeErrorMessage(err)
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, errText)
|
||||
return connection.QueryResult{
|
||||
Success: false,
|
||||
Message: logDriverOperationError(err, "写入本地驱动元数据失败,driver=%s", definition.Type),
|
||||
}
|
||||
}
|
||||
a.emitDriverDownloadProgress(definition.Type, "done", 1, 1, "安装完成(纯 Go 驱动已启用)")
|
||||
a.emitDriverDownloadProgress(definition.Type, "done", 100, 100, "本地驱动包导入完成")
|
||||
|
||||
return connection.QueryResult{Success: true, Message: "驱动安装成功", Data: map[string]interface{}{
|
||||
"driverType": definition.Type,
|
||||
@@ -683,13 +747,21 @@ func (a *App) DownloadDriverPackage(driverType string, version string, downloadU
|
||||
a.emitDriverDownloadProgress(definition.Type, "start", 0, 100, fmt.Sprintf("开始安装 %s 驱动代理", displayName))
|
||||
meta, installErr := installOptionalDriverAgentPackage(a, definition, selectedVersion, resolvedDir, urlText)
|
||||
if installErr != nil {
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, installErr.Error())
|
||||
return connection.QueryResult{Success: false, Message: installErr.Error()}
|
||||
errText := normalizeErrorMessage(installErr)
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, errText)
|
||||
return connection.QueryResult{
|
||||
Success: false,
|
||||
Message: logDriverOperationError(installErr, "驱动下载安装失败,driver=%s version=%s url=%s", definition.Type, selectedVersion, urlText),
|
||||
}
|
||||
}
|
||||
a.emitDriverDownloadProgress(definition.Type, "downloading", 95, 100, "写入驱动元数据")
|
||||
if writeErr := writeInstalledDriverPackage(resolvedDir, definition.Type, meta); writeErr != nil {
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, writeErr.Error())
|
||||
return connection.QueryResult{Success: false, Message: writeErr.Error()}
|
||||
errText := normalizeErrorMessage(writeErr)
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, errText)
|
||||
return connection.QueryResult{
|
||||
Success: false,
|
||||
Message: logDriverOperationError(writeErr, "写入驱动元数据失败,driver=%s version=%s", definition.Type, selectedVersion),
|
||||
}
|
||||
}
|
||||
a.emitDriverDownloadProgress(definition.Type, "done", 100, 100, fmt.Sprintf("%s 驱动代理安装完成", displayName))
|
||||
return connection.QueryResult{Success: true, Message: "驱动安装成功", Data: map[string]interface{}{
|
||||
@@ -710,8 +782,12 @@ func (a *App) DownloadDriverPackage(driverType string, version string, downloadU
|
||||
DownloadedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
if err := writeInstalledDriverPackage(resolvedDir, definition.Type, meta); err != nil {
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, err.Error())
|
||||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||||
errText := normalizeErrorMessage(err)
|
||||
a.emitDriverDownloadProgress(definition.Type, "error", 0, 0, errText)
|
||||
return connection.QueryResult{
|
||||
Success: false,
|
||||
Message: logDriverOperationError(err, "写入驱动元数据失败,driver=%s version=%s", definition.Type, selectedVersion),
|
||||
}
|
||||
}
|
||||
a.emitDriverDownloadProgress(definition.Type, "done", 1, 1, "安装完成(纯 Go 驱动已启用)")
|
||||
|
||||
@@ -781,6 +857,100 @@ func (a *App) emitDriverDownloadProgress(driverType string, status string, downl
|
||||
runtime.EventsEmit(a.ctx, driverDownloadProgressEvent, payload)
|
||||
}
|
||||
|
||||
func probeDriverNetworkEndpoint(item driverNetworkProbeItem) driverNetworkProbeItem {
|
||||
probed := item
|
||||
probed.Reachable = false
|
||||
probed.HTTPStatus = 0
|
||||
probed.Error = ""
|
||||
probed.LatencyMs = 0
|
||||
|
||||
urlText := strings.TrimSpace(item.URL)
|
||||
if urlText == "" {
|
||||
probed.Error = "检测地址为空"
|
||||
return probed
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: driverNetworkProbeTimeout}
|
||||
start := time.Now()
|
||||
req, err := http.NewRequest(http.MethodHead, urlText, nil)
|
||||
if err != nil {
|
||||
probed.Error = normalizeErrorMessage(err)
|
||||
return probed
|
||||
}
|
||||
req.Header.Set("User-Agent", "GoNavi-DriverManager")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// 某些网关不支持 HEAD,请回退为 GET(不读取正文)。
|
||||
reqGet, reqErr := http.NewRequest(http.MethodGet, urlText, nil)
|
||||
if reqErr != nil {
|
||||
probed.Error = normalizeErrorMessage(reqErr)
|
||||
probed.LatencyMs = time.Since(start).Milliseconds()
|
||||
return probed
|
||||
}
|
||||
reqGet.Header.Set("User-Agent", "GoNavi-DriverManager")
|
||||
resp, err = client.Do(reqGet)
|
||||
}
|
||||
probed.LatencyMs = time.Since(start).Milliseconds()
|
||||
if err != nil {
|
||||
probed.Error = normalizeDriverNetworkError(err)
|
||||
return probed
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
probed.HTTPStatus = resp.StatusCode
|
||||
if resp.StatusCode >= 500 {
|
||||
probed.Error = fmt.Sprintf("HTTP %d", resp.StatusCode)
|
||||
return probed
|
||||
}
|
||||
probed.Reachable = true
|
||||
return probed
|
||||
}
|
||||
|
||||
func normalizeDriverNetworkError(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||
return "网络连接超时"
|
||||
}
|
||||
return normalizeErrorMessage(err)
|
||||
}
|
||||
|
||||
func collectDriverProxyEnv() map[string]string {
|
||||
keys := []string{
|
||||
"HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY",
|
||||
"http_proxy", "https_proxy", "all_proxy", "no_proxy",
|
||||
}
|
||||
result := make(map[string]string)
|
||||
for _, key := range keys {
|
||||
value := strings.TrimSpace(os.Getenv(key))
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func driverLogHint() string {
|
||||
path := strings.TrimSpace(logger.Path())
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("(详细日志:%s)", path)
|
||||
}
|
||||
|
||||
func logDriverOperationError(err error, format string, args ...interface{}) string {
|
||||
message := normalizeErrorMessage(err)
|
||||
if strings.TrimSpace(message) == "" {
|
||||
message = "未知错误"
|
||||
}
|
||||
logger.Error(err, format, args...)
|
||||
return strings.TrimSpace(message) + driverLogHint()
|
||||
}
|
||||
|
||||
func defaultDriverDownloadDirectory() string {
|
||||
root, err := db.ResolveExternalDriverRoot("")
|
||||
if err == nil && strings.TrimSpace(root) != "" {
|
||||
@@ -1378,12 +1548,7 @@ func fetchGoModuleVersionMetas(modulePath string) ([]goModuleVersionMeta, error)
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("https://proxy.golang.org/%s/@v/list", escapeGoModulePathForProxy(trimmed))
|
||||
client := &http.Client{
|
||||
Timeout: driverModuleLatestProbeTimeout,
|
||||
Transport: &http.Transport{
|
||||
Proxy: nil,
|
||||
},
|
||||
}
|
||||
client := &http.Client{Timeout: driverModuleLatestProbeTimeout}
|
||||
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2025,6 +2190,141 @@ func installOptionalDriverAgentPackage(a *App, definition driverDefinition, sele
|
||||
}, nil
|
||||
}
|
||||
|
||||
func installOptionalDriverAgentFromLocalFile(definition driverDefinition, filePath string, resolvedDir string, selectedVersion string) (installedDriverPackage, error) {
|
||||
driverType := normalizeDriverType(definition.Type)
|
||||
displayName := resolveDriverDisplayName(definition)
|
||||
pathText := strings.TrimSpace(filePath)
|
||||
if pathText == "" {
|
||||
return installedDriverPackage{}, fmt.Errorf("本地驱动包路径为空")
|
||||
}
|
||||
if absPath, absErr := filepath.Abs(pathText); absErr == nil {
|
||||
pathText = absPath
|
||||
}
|
||||
info, statErr := os.Stat(pathText)
|
||||
if statErr != nil {
|
||||
return installedDriverPackage{}, fmt.Errorf("读取本地驱动包失败:%w", statErr)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return installedDriverPackage{}, fmt.Errorf("本地驱动包路径为目录:%s", pathText)
|
||||
}
|
||||
|
||||
executablePath, err := db.ResolveOptionalDriverAgentExecutablePath(resolvedDir, driverType)
|
||||
if err != nil {
|
||||
return installedDriverPackage{}, err
|
||||
}
|
||||
if mkErr := os.MkdirAll(filepath.Dir(executablePath), 0o755); mkErr != nil {
|
||||
return installedDriverPackage{}, fmt.Errorf("创建 %s 驱动目录失败:%w", displayName, mkErr)
|
||||
}
|
||||
|
||||
downloadSource := fmt.Sprintf("local://manual/%s", filepath.Base(pathText))
|
||||
if strings.EqualFold(filepath.Ext(pathText), ".zip") {
|
||||
entryName, extractErr := installOptionalDriverAgentFromLocalZip(pathText, definition, executablePath)
|
||||
if extractErr != nil {
|
||||
return installedDriverPackage{}, extractErr
|
||||
}
|
||||
if strings.TrimSpace(entryName) != "" {
|
||||
downloadSource = downloadSource + "#" + entryName
|
||||
}
|
||||
} else {
|
||||
if copyErr := copyAgentBinary(pathText, executablePath); copyErr != nil {
|
||||
return installedDriverPackage{}, fmt.Errorf("导入本地驱动代理失败:%w", copyErr)
|
||||
}
|
||||
}
|
||||
|
||||
hash, hashErr := hashFileSHA256(executablePath)
|
||||
if hashErr != nil {
|
||||
return installedDriverPackage{}, fmt.Errorf("计算 %s 驱动代理摘要失败:%w", displayName, hashErr)
|
||||
}
|
||||
return installedDriverPackage{
|
||||
DriverType: driverType,
|
||||
Version: strings.TrimSpace(selectedVersion),
|
||||
FilePath: pathText,
|
||||
FileName: filepath.Base(pathText),
|
||||
ExecutablePath: executablePath,
|
||||
DownloadURL: downloadSource,
|
||||
SHA256: hash,
|
||||
DownloadedAt: time.Now().Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func installOptionalDriverAgentFromLocalZip(zipPath string, definition driverDefinition, executablePath string) (string, error) {
|
||||
driverType := normalizeDriverType(definition.Type)
|
||||
displayName := resolveDriverDisplayName(definition)
|
||||
reader, err := zip.OpenReader(zipPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("打开本地驱动包失败:%w", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
entryPath := optionalDriverBundleEntryPath(driverType)
|
||||
expectedBaseName := optionalDriverReleaseAssetName(driverType)
|
||||
findEntry := func() *zip.File {
|
||||
for _, file := range reader.File {
|
||||
name := filepath.ToSlash(strings.TrimPrefix(strings.TrimSpace(file.Name), "./"))
|
||||
if name == entryPath {
|
||||
return file
|
||||
}
|
||||
}
|
||||
for _, file := range reader.File {
|
||||
name := filepath.ToSlash(strings.TrimPrefix(strings.TrimSpace(file.Name), "./"))
|
||||
if strings.EqualFold(name, entryPath) {
|
||||
return file
|
||||
}
|
||||
}
|
||||
for _, file := range reader.File {
|
||||
name := filepath.ToSlash(strings.TrimPrefix(strings.TrimSpace(file.Name), "./"))
|
||||
if strings.EqualFold(filepath.Base(name), expectedBaseName) {
|
||||
return file
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := findEntry()
|
||||
if entry == nil {
|
||||
return "", fmt.Errorf("本地驱动包内未找到 %s 代理文件(期望路径 %s)", displayName, entryPath)
|
||||
}
|
||||
|
||||
src, err := entry.Open()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取本地驱动包条目失败:%w", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
tempPath := executablePath + ".tmp"
|
||||
_ = os.Remove(tempPath)
|
||||
dst, err := os.Create(tempPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建驱动代理临时文件失败:%w", err)
|
||||
}
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
dst.Close()
|
||||
_ = os.Remove(tempPath)
|
||||
return "", fmt.Errorf("写入驱动代理失败:%w", err)
|
||||
}
|
||||
if err := dst.Sync(); err != nil {
|
||||
dst.Close()
|
||||
_ = os.Remove(tempPath)
|
||||
return "", fmt.Errorf("落盘驱动代理失败:%w", err)
|
||||
}
|
||||
if err := dst.Close(); err != nil {
|
||||
_ = os.Remove(tempPath)
|
||||
return "", fmt.Errorf("关闭驱动代理文件失败:%w", err)
|
||||
}
|
||||
if chmodErr := os.Chmod(tempPath, 0o755); chmodErr != nil && stdRuntime.GOOS != "windows" {
|
||||
_ = os.Remove(tempPath)
|
||||
return "", fmt.Errorf("设置驱动代理权限失败:%w", chmodErr)
|
||||
}
|
||||
if err := os.Rename(tempPath, executablePath); err != nil {
|
||||
_ = os.Remove(tempPath)
|
||||
return "", fmt.Errorf("替换驱动代理失败:%w", err)
|
||||
}
|
||||
if chmodErr := os.Chmod(executablePath, 0o755); chmodErr != nil && stdRuntime.GOOS != "windows" {
|
||||
return "", fmt.Errorf("设置驱动代理权限失败:%w", chmodErr)
|
||||
}
|
||||
return filepath.ToSlash(strings.TrimPrefix(strings.TrimSpace(entry.Name), "./")), nil
|
||||
}
|
||||
|
||||
func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, executablePath string, downloadURL string) (string, string, error) {
|
||||
driverType := normalizeDriverType(definition.Type)
|
||||
displayName := resolveDriverDisplayName(definition)
|
||||
|
||||
Reference in New Issue
Block a user