diff --git a/web/src/admin/AdminPage.tsx b/web/src/admin/AdminPage.tsx index 0c3254f..cd70ee9 100644 --- a/web/src/admin/AdminPage.tsx +++ b/web/src/admin/AdminPage.tsx @@ -1,5 +1,5 @@ -import { useCallback, useEffect, useState, type FormEvent } from 'react' -import { adminApi, ApiError, type AccountView, type ConnInfo } from '../api' +import { useCallback, useEffect, useRef, useState, type FormEvent } from 'react' +import { adminApi, ApiError, type AccountView, type AuthMode, type ConnInfo } from '../api' import { formatSize } from '../format' import { IconCopy, @@ -36,6 +36,12 @@ function connText(conn: ConnInfo): string { return `地址:${conn.url}\n用户名:${conn.user}\n口令:${conn.pass}` } +// SSO 模式下 401 统一交给网关登录页处理。 +function redirectToGatewayLogin() { + const next = window.location.pathname + window.location.search + window.location.href = `/_auth/login?next=${encodeURIComponent(next)}` +} + function Switch({ checked, onChange, @@ -98,6 +104,7 @@ function ConnModal({ conn, onClose }: { conn: ConnInfo; onClose: () => void }) { } export function AdminPage() { + const [mode, setMode] = useState(null) const [ready, setReady] = useState(false) const [authed, setAuthed] = useState(false) const [password, setPassword] = useState('') @@ -108,6 +115,15 @@ export function AdminPage() { const [showNew, setShowNew] = useState(false) const [newUser, setNewUser] = useState('') const [newReadonly, setNewReadonly] = useState(false) + const modeRef = useRef('builtin') + + const handleUnauthorized = useCallback(() => { + if (modeRef.current === 'sso') { + redirectToGatewayLogin() + return + } + setAuthed(false) + }, []) const load = useCallback(async () => { try { @@ -116,17 +132,33 @@ export function AdminPage() { setAuthed(true) } catch (err) { if (err instanceof ApiError && err.status === 401) { - setAuthed(false) + handleUnauthorized() } else { setError(err instanceof Error ? err.message : '加载失败') } } finally { setReady(true) } - }, []) + }, [handleUnauthorized]) useEffect(() => { - void load() + let alive = true + void (async () => { + let detected: AuthMode = 'builtin' + try { + const info = await adminApi.mode() + if (info.mode === 'sso') detected = 'sso' + } catch { + /* 取不到模式时按 builtin 处理,保持默认行为 */ + } + if (!alive) return + modeRef.current = detected + setMode(detected) + await load() + })() + return () => { + alive = false + } }, [load]) const run = async (fn: () => Promise) => { @@ -136,7 +168,7 @@ export function AdminPage() { await fn() } catch (err) { if (err instanceof ApiError && err.status === 401) { - setAuthed(false) + handleUnauthorized() } else { setError(err instanceof Error ? err.message : '操作失败') } @@ -155,6 +187,10 @@ export function AdminPage() { } const logout = () => { + if (mode === 'sso') { + window.location.href = '/_auth/logout' + return + } void run(async () => { await adminApi.logout() setAuthed(false) @@ -206,7 +242,7 @@ export function AdminPage() { }) } - if (!ready) { + if (!ready || mode === null) { return (
加载中
@@ -215,6 +251,14 @@ export function AdminPage() { } if (!authed) { + // SSO 模式不显示口令输入框;未认证时已跳转网关登录页。 + if (mode === 'sso') { + return ( +
+
正在跳转登录
+
+ ) + } return (
diff --git a/web/src/api.ts b/web/src/api.ts index f265013..aacfe03 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -14,6 +14,8 @@ export interface ConnInfo { pass: string } +export type AuthMode = 'builtin' | 'sso' + export interface Entry { name: string isDir: boolean @@ -58,6 +60,7 @@ async function request(method: string, url: string, body?: unknown): Promise< export { ApiError } export const adminApi = { + mode: () => request<{ mode: AuthMode }>('GET', '/api/admin/mode'), login: (password: string) => request<{ ok: boolean }>('POST', '/api/admin/login', { password }), logout: () => request<{ ok: boolean }>('POST', '/api/admin/logout'), list: () => request('GET', '/api/admin/accounts'),