Compare commits

...

2 Commits

Author SHA1 Message Date
Tunglies
36624aff49
fix(logs): preserve log data and eliminate blank flash on page navigation
Preserve SWR cache in onConnected to avoid replacing accumulated logs
with kernel buffer on reconnect. Add KeepAlive for the logs page so
its DOM stays mounted across route changes, removing the visible blank
window when navigating back.
2026-04-03 13:13:57 +08:00
wonfen
51578c03b0
fix: URL test url 2026-04-03 12:13:04 +08:00
9 changed files with 49 additions and 14 deletions

View File

@ -252,15 +252,27 @@ impl SilentUpdater {
true
}
Ok(Ok(Err(e))) => {
logging!(warn, Type::System, "Startup install failed: {e}, will retry next launch");
logging!(
warn,
Type::System,
"Startup install failed: {e}, will retry next launch"
);
false
}
Ok(Err(e)) => {
logging!(warn, Type::System, "Startup install task panicked: {e}, will retry next launch");
logging!(
warn,
Type::System,
"Startup install task panicked: {e}, will retry next launch"
);
false
}
Err(_) => {
logging!(warn, Type::System, "Startup install timed out (30s), will retry next launch");
logging!(
warn,
Type::System,
"Startup install timed out (30s), will retry next launch"
);
false
}
};
@ -283,8 +295,7 @@ impl SilentUpdater {
use tauri_plugin_dialog::{DialogExt as _, MessageDialogButtons, MessageDialogKind};
let title = clash_verge_i18n::t!("notifications.updateReady.title").to_string();
let body = clash_verge_i18n::t!("notifications.updateReady.body")
.replace("{version}", version);
let body = clash_verge_i18n::t!("notifications.updateReady.body").replace("{version}", version);
let install_now = clash_verge_i18n::t!("notifications.updateReady.installNow").to_string();
let later = clash_verge_i18n::t!("notifications.updateReady.later").to_string();

View File

@ -614,7 +614,7 @@ export const GroupsEditorViewer = (props: Props) => {
/>
<TextField
autoComplete="new-password"
placeholder="http://cp.cloudflare.com"
placeholder="http://cp.cloudflare.com/generate_204"
size="small"
sx={{ width: 'calc(100% - 150px)' }}
{...field}

View File

@ -66,7 +66,8 @@ export const ProxyHead = ({
const { verge } = useVerge()
const defaultLatencyUrl =
verge?.default_latency_test?.trim() || 'http://cp.cloudflare.com'
verge?.default_latency_test?.trim() ||
'http://cp.cloudflare.com/generate_204'
useEffect(() => {
delayManager.setUrl(groupName, testUrl?.trim() || url || defaultLatencyUrl)

View File

@ -383,7 +383,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
spellCheck="false"
sx={{ width: 250, marginLeft: 'auto' }}
value={values.defaultLatencyTest}
placeholder="http://cp.cloudflare.com"
placeholder="http://cp.cloudflare.com/generate_204"
onChange={(e) =>
setValues((v) => ({ ...v, defaultLatencyTest: e.target.value }))
}

View File

@ -101,7 +101,12 @@ export const useLogData = () => {
async onConnected() {
const logs = await getClashLogs()
if (isMounted()) {
next(null, clampLogs(filterLogsByLevel(logs, allowedTypes)))
next(null, (current) => {
if (!current || current.length === 0) {
return clampLogs(filterLogsByLevel(logs, allowedTypes))
}
return current
})
}
},
cleanup: clearFlushTimer,

View File

@ -26,7 +26,7 @@ import relativeTime from 'dayjs/plugin/relativeTime'
import type { CSSProperties } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Outlet, useNavigate } from 'react-router'
import { Outlet, useLocation, useNavigate } from 'react-router'
import { SWRConfig } from 'swr'
import iconDark from '@/assets/image/icon_dark.svg?react'
@ -53,6 +53,7 @@ import {
} from './_layout/hooks'
import { handleNoticeMessage } from './_layout/utils'
import { navItems } from './_routers'
import LogsPage from './logs'
import 'dayjs/locale/ru'
import 'dayjs/locale/zh-cn'
@ -120,6 +121,10 @@ const Layout = () => {
const navCollapsed = verge?.collapse_navbar ?? false
const { switchLanguage } = useI18n()
const navigate = useNavigate()
const { pathname } = useLocation()
const isLogsPage = pathname === '/logs'
const logsPageMountedRef = useRef(false)
if (isLogsPage) logsPageMountedRef.current = true
const themeReady = useMemo(() => Boolean(theme), [theme])
const [menuUnlocked, setMenuUnlocked] = useState(false)
@ -477,6 +482,20 @@ const Layout = () => {
<BaseErrorBoundary>
<Outlet />
</BaseErrorBoundary>
{logsPageMountedRef.current && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
display: isLogsPage ? undefined : 'none',
}}
>
<LogsPage />
</div>
)}
</div>
</div>
</div>

View File

@ -20,7 +20,6 @@ import UnlockSvg from '@/assets/image/itemicon/unlock.svg?react'
import Layout from './_layout'
import ConnectionsPage from './connections'
import HomePage from './home'
import LogsPage from './logs'
import ProfilesPage from './profiles'
import ProxiesPage from './proxies'
import RulesPage from './rules'
@ -62,7 +61,7 @@ export const navItems = [
label: 'layout.components.navigation.tabs.logs',
path: '/logs',
icon: [<SubjectRoundedIcon key="mui" />, <LogsSvg key="svg" />],
Component: LogsPage,
Component: () => null /* KeepAlive: real LogsPage rendered in Layout */,
},
{
label: 'layout.components.navigation.tabs.unlock',

View File

@ -340,7 +340,7 @@ export async function cmdGetProxyDelay(
url?: string,
) {
// 确保URL不为空
const testUrl = url || 'http://cp.cloudflare.com'
const testUrl = url || 'http://cp.cloudflare.com/generate_204'
try {
// 不再在前端编码代理名称,由后端统一处理编码

View File

@ -120,7 +120,7 @@ class DelayManager {
`[DelayManager] 获取测试URL组: ${group}, URL: ${url || '未设置'}`,
)
// 如果未设置URL返回默认URL
return url || 'http://cp.cloudflare.com'
return url || 'http://cp.cloudflare.com/generate_204'
}
setListener(