fix(frontend): stabilize tauri listeners and logging payloads

This commit is contained in:
2026-03-17 22:22:45 +08:00
parent d12fb33b2a
commit feb3542728
12 changed files with 159 additions and 50 deletions
+1
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>SecScore</title>
</head>
+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="12" fill="#0052D9" />
<path
d="M16 22c0-3.314 2.686-6 6-6h20c3.314 0 6 2.686 6 6v20c0 3.314-2.686 6-6 6H22c-3.314 0-6-2.686-6-6V22z"
fill="#ffffff"
/>
<path
d="M24 28h16a2 2 0 110 4H24a2 2 0 110-4zm0 8h10a2 2 0 110 4H24a2 2 0 110-4z"
fill="#0052D9"
/>
</svg>

After

Width:  |  Height:  |  Size: 379 B

+1 -1
View File
@@ -9,9 +9,9 @@ fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.setup(|app| {
setup_app(app)?;
let state = AppState::new(app.handle().clone());
app.manage(Arc::new(RwLock::new(state)));
setup_app(app)?;
Ok(())
})
.invoke_handler(tauri::generate_handler![
+21 -3
View File
@@ -15,8 +15,14 @@ function MainContent(): React.JSX.Element {
const [messageApi, contextHolder] = message.useMessage()
useEffect(() => {
if (!(window as any).api) return
const unlisten = (window as any).api.onNavigate((route: string) => {
const api = (window as any).api
if (!api) return
let disposed = false
let unlisten: (() => void) | null = null
api
.onNavigate((route: string) => {
const currentPath = location.pathname === "/" ? "/home" : location.pathname
const targetPath = route === "/" ? "/home" : route
@@ -24,7 +30,19 @@ function MainContent(): React.JSX.Element {
navigate(route)
}
})
return () => unlisten()
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
}
}, [navigate, location.pathname])
const [wizardVisible, setWizardVisible] = useState(false)
+19 -3
View File
@@ -95,8 +95,14 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
useEffect(() => {
loadAll()
if (!(window as any).api) return
const unsubscribe = (window as any).api.onSettingChanged((change: any) => {
const api = (window as any).api
if (!api) return
let disposed = false
let unlisten: (() => void) | null = null
api
.onSettingChanged((change: any) => {
setSettings((prev) => {
if (change?.key === "log_level") return { ...prev, log_level: change.value }
if (change?.key === "is_wizard_completed")
@@ -107,8 +113,18 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
return prev
})
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
if (typeof unsubscribe === "function") unsubscribe()
disposed = true
if (unlisten) unlisten()
}
}, [])
+20 -5
View File
@@ -39,15 +39,30 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps):
const handleStatusChange = () => {
loadDbStatus()
}
if ((window as any).api) {
const unsubscribe = (window as any).api.onSettingChanged(
(change: { key: string; value: any }) => {
const api = (window as any).api
if (!api) return
let disposed = false
let unlisten: (() => void) | null = null
api
.onSettingChanged((change: { key: string; value: any }) => {
if (change.key === "pg_connection_status") {
handleStatusChange()
}
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
)
return unsubscribe
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
}
}, [])
+25 -4
View File
@@ -16,13 +16,34 @@ export function TitleBar({ children }: TitleBarProps): React.JSX.Element {
const [isMaximized, setIsMaximized] = useState(false)
useEffect(() => {
if (!(window as any).api) return
;(window as any).api.windowIsMaximized().then((v: boolean) => setIsMaximized(v))
const api = (window as any).api
if (!api) return
const cleanup = (window as any).api.onWindowMaximizedChanged((maximized: boolean) => {
let disposed = false
let unlisten: (() => void) | null = null
api
.windowIsMaximized()
.then((v: boolean) => setIsMaximized(v))
.catch(() => void 0)
api
.onWindowMaximizedChanged((maximized: boolean) => {
setIsMaximized(maximized)
})
return cleanup
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
}
}, [])
const minimize = () => {
+6
View File
@@ -14,6 +14,7 @@ export function WindowControls(): React.JSX.Element {
const api = (window as any).api
if (!api) return
let disposed = false
let unlisten: (() => void) | null = null
api
@@ -26,11 +27,16 @@ export function WindowControls(): React.JSX.Element {
setIsMaximized(maximized)
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
disposed = true
if (unlisten) unlisten()
}
}, [])
+18 -5
View File
@@ -120,23 +120,36 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre
}, [applyThemeConfig])
useEffect(() => {
if (!(window as any).api) return
const api = (window as any).api
if (!api) return
;(async () => {
await loadThemes()
await loadCurrentTheme()
})()
const unsubscribe = (window as any).api.onThemeChanged((theme: themeConfig) => {
let disposed = false
let unlisten: (() => void) | null = null
api
.onThemeChanged((theme: themeConfig) => {
setCurrentTheme(theme)
currentThemeRef.current = theme
applyThemeConfig(theme)
loadThemes()
})
.then((fn: () => void) => {
if (disposed) {
fn()
return
}
unlisten = fn
})
.catch(() => void 0)
return () => {
if (typeof unsubscribe === "function") {
unsubscribe()
}
disposed = true
if (unlisten) unlisten()
}
}, [applyThemeConfig, loadCurrentTheme, loadThemes])
+1 -1
View File
@@ -24,7 +24,7 @@ const safeWriteLog = (payload: {
try {
const api = (window as any).api
if (!api?.writeLog) return
api.writeLog(payload)
Promise.resolve(api.writeLog(payload)).catch(() => void 0)
} catch {
return
}
+7 -3
View File
@@ -188,15 +188,19 @@ const api = {
},
// Logger
queryLogs: (params?: { lines?: number }): Promise<{ success: boolean; data: string[] }> =>
invoke("log_query", { params }),
queryLogs: (
input?: number | { lines?: number }
): Promise<{ success: boolean; data: string[] }> => {
const lines = typeof input === "number" ? input : input?.lines
return invoke("log_query", { lines })
},
clearLogs: (): Promise<{ success: boolean }> => invoke("log_clear"),
setLogLevel: (level: string): Promise<{ success: boolean }> => invoke("log_set_level", { level }),
writeLog: (payload: {
level: string
message: string
meta?: any
}): Promise<{ success: boolean }> => invoke("log_write", payload),
}): Promise<{ success: boolean }> => invoke("log_write", { payload }),
// Database Connection
dbTestConnection: (
+4
View File
@@ -9,6 +9,10 @@ export default defineConfig({
"@": resolve(__dirname, "src"),
},
},
optimizeDeps: {
// Avoid scanning legacy `old-ss` entries under project root.
entries: ["index.html"],
},
build: {
outDir: "dist",
emptyOutDir: true,