diff --git a/cmd/optional-driver-agent/provider_clickhouse.go b/cmd/optional-driver-agent/provider_clickhouse.go
new file mode 100644
index 0000000..0df04ba
--- /dev/null
+++ b/cmd/optional-driver-agent/provider_clickhouse.go
@@ -0,0 +1,12 @@
+//go:build gonavi_clickhouse_driver
+
+package main
+
+import "GoNavi-Wails/internal/db"
+
+func init() {
+ agentDriverType = "clickhouse"
+ agentDatabaseFactory = func() db.Database {
+ return &db.ClickHouseDB{}
+ }
+}
diff --git a/docs/driver-manifest.json b/docs/driver-manifest.json
index a1c0c7c..aaef2d8 100644
--- a/docs/driver-manifest.json
+++ b/docs/driver-manifest.json
@@ -73,6 +73,12 @@
"checksumPolicy": "off",
"downloadUrl": "builtin://activate/tdengine"
},
+ "clickhouse": {
+ "engine": "go",
+ "version": "2.43.0",
+ "checksumPolicy": "off",
+ "downloadUrl": "builtin://activate/clickhouse"
+ },
"postgres": {
"engine": "go",
"version": "1.11.1",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 0d8db66..c189de5 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
-import { Layout, Button, ConfigProvider, theme, Dropdown, MenuProps, message, Modal, Spin, Slider, Progress, Switch } from 'antd';
+import { Layout, Button, ConfigProvider, theme, Dropdown, MenuProps, message, Modal, Spin, Slider, Progress, Switch, Input, InputNumber, Select } from 'antd';
import zhCN from 'antd/locale/zh_CN';
-import { PlusOutlined, BulbOutlined, BulbFilled, ConsoleSqlOutlined, UploadOutlined, DownloadOutlined, CloudDownloadOutlined, BugOutlined, ToolOutlined, InfoCircleOutlined, GithubOutlined, SkinOutlined, CheckOutlined, MinusOutlined, BorderOutlined, CloseOutlined, SettingOutlined } from '@ant-design/icons';
+import { PlusOutlined, BulbOutlined, BulbFilled, ConsoleSqlOutlined, UploadOutlined, DownloadOutlined, CloudDownloadOutlined, BugOutlined, ToolOutlined, GlobalOutlined, InfoCircleOutlined, GithubOutlined, SkinOutlined, CheckOutlined, MinusOutlined, BorderOutlined, CloseOutlined, SettingOutlined } from '@ant-design/icons';
import { Environment, EventsOn, WindowFullscreen, WindowIsFullscreen, WindowIsMaximised, WindowMaximise } from '../wailsjs/runtime/runtime';
import Sidebar from './components/Sidebar';
import TabManager from './components/TabManager';
@@ -12,7 +12,7 @@ import LogPanel from './components/LogPanel';
import { useStore } from './store';
import { SavedConnection } from './types';
import { blurToFilter, normalizeBlurForPlatform, normalizeOpacityForPlatform, isWindowsPlatform } from './utils/appearance';
-import { SetWindowTranslucency } from '../wailsjs/go/app/App';
+import { ConfigureGlobalProxy, SetWindowTranslucency } from '../wailsjs/go/app/App';
import './App.css';
const { Sider, Content } = Layout;
@@ -28,12 +28,16 @@ function App() {
const setAppearance = useStore(state => state.setAppearance);
const startupFullscreen = useStore(state => state.startupFullscreen);
const setStartupFullscreen = useStore(state => state.setStartupFullscreen);
+ const globalProxy = useStore(state => state.globalProxy);
+ const setGlobalProxy = useStore(state => state.setGlobalProxy);
const darkMode = themeMode === 'dark';
const effectiveOpacity = normalizeOpacityForPlatform(appearance.opacity);
const effectiveBlur = normalizeBlurForPlatform(appearance.blur);
const blurFilter = blurToFilter(effectiveBlur);
const windowCornerRadius = 14;
const [isLinuxRuntime, setIsLinuxRuntime] = useState(false);
+ const [isStoreHydrated, setIsStoreHydrated] = useState(() => useStore.persist.hasHydrated());
+ const globalProxyInvalidHintShownRef = React.useRef(false);
// 同步 macOS 窗口透明度:opacity=1.0 且 blur=0 时关闭 NSVisualEffectView,
// 避免 GPU 持续计算窗口背后的模糊合成
@@ -58,6 +62,83 @@ function App() {
};
}, []);
+ useEffect(() => {
+ if (isStoreHydrated) {
+ return;
+ }
+ const unsubscribe = useStore.persist.onFinishHydration(() => {
+ setIsStoreHydrated(true);
+ });
+ return () => {
+ unsubscribe();
+ };
+ }, [isStoreHydrated]);
+
+ useEffect(() => {
+ if (!isStoreHydrated) {
+ return;
+ }
+
+ const host = String(globalProxy.host || '').trim();
+ const port = Number(globalProxy.port);
+ const portValid = Number.isFinite(port) && port > 0 && port <= 65535;
+ const invalidWhenEnabled = globalProxy.enabled && (!host || !portValid);
+
+ if (invalidWhenEnabled) {
+ if (!globalProxyInvalidHintShownRef.current) {
+ message.warning({
+ content: '全局代理已开启,但地址或端口无效,当前按未启用处理',
+ key: 'global-proxy-invalid',
+ });
+ globalProxyInvalidHintShownRef.current = true;
+ }
+ } else {
+ globalProxyInvalidHintShownRef.current = false;
+ message.destroy('global-proxy-invalid');
+ }
+
+ const enabledForBackend = globalProxy.enabled && !invalidWhenEnabled;
+ let cancelled = false;
+ ConfigureGlobalProxy(enabledForBackend, {
+ type: globalProxy.type,
+ host,
+ port: portValid ? port : (globalProxy.type === 'http' ? 8080 : 1080),
+ user: String(globalProxy.user || '').trim(),
+ password: globalProxy.password || '',
+ })
+ .then((res) => {
+ if (cancelled || res?.success) {
+ return;
+ }
+ message.error({
+ content: '全局代理配置失败: ' + (res?.message || '未知错误'),
+ key: 'global-proxy-sync-error',
+ });
+ })
+ .catch((err) => {
+ if (cancelled) {
+ return;
+ }
+ const errMsg = err instanceof Error ? err.message : String(err || '未知错误');
+ message.error({
+ content: '全局代理配置失败: ' + errMsg,
+ key: 'global-proxy-sync-error',
+ });
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, [
+ isStoreHydrated,
+ globalProxy.enabled,
+ globalProxy.type,
+ globalProxy.host,
+ globalProxy.port,
+ globalProxy.user,
+ globalProxy.password,
+ ]);
+
useEffect(() => {
let cancelled = false;
let startupWindowTimer: number | null = null;
@@ -492,6 +573,7 @@ function App() {
];
const [isAppearanceModalOpen, setIsAppearanceModalOpen] = useState(false);
+ const [isProxyModalOpen, setIsProxyModalOpen] = useState(false);
// Log Panel: 最小高度按“工具栏 + 1 条日志行(微增)”限制
@@ -814,6 +896,7 @@ function App() {