feat: revamp web hub — global connection status, array config editor, log streaming
- Global connection badge in app header (persistent dashboard SSE via ConnectionProvider) - LogProvider keeps log data and SSE stream alive across page switches (no reload spinner) - Config drawer: password reveal toggle, array-type row editor for list fields - Plugin management: optimistic updates, race-free loading, reset all group overrides - Logs: load-earlier pagination; backend tail-read optimization (before/end offsets) - web_auth: Web password changes persist (sync_admin seeding only when .env has explicit key) - Dashboard: collect in thread pool + 1s result cache - ErrorBoundary + local Suspense: plugin chunk loading stays inside content area - UI polish: table row hover, mobile header/breadcrumb fixes, wider config drawer - config_standard: array type inference for list fields; array env skip - random_jm_code / steam_info: array schema migration; README web frontend note Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Button } from '@heroui/react'
|
||||
import { useAuth } from './auth'
|
||||
|
||||
export function LoginPage() {
|
||||
const { login } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
// 被 AppLayout 带到登录页时记住原目标,登录后回跳
|
||||
const from = (location.state as any)?.from || '/hub'
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [err, setErr] = useState('')
|
||||
@@ -15,7 +18,7 @@ export function LoginPage() {
|
||||
setLoading(true); setErr('')
|
||||
try {
|
||||
await login(username, password)
|
||||
navigate('/hub')
|
||||
navigate(from, { replace: true })
|
||||
} catch (e: any) {
|
||||
setErr(e.message || '登录失败')
|
||||
} finally { setLoading(false) }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createContext, useContext, useState, type ReactNode } from 'react'
|
||||
import { authLogin, clearSession, getUserName, setSession } from '../api/client'
|
||||
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
|
||||
import { authLogin, authLogout, AUTH_EXPIRED_EVENT, clearSession, getUserName, setSession, TOKEN_KEY, USER_KEY } from '../api/client'
|
||||
|
||||
interface AuthState {
|
||||
token: string
|
||||
@@ -11,8 +11,8 @@ interface AuthState {
|
||||
const Ctx = createContext<AuthState>({ token: '', username: '', login: async () => {}, logout: () => {} })
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [token, setToken] = useState(() => localStorage.getItem('hexi_web_token') || '')
|
||||
const [username, setUsername] = useState(() => localStorage.getItem('hexi_web_username') || '')
|
||||
const [token, setToken] = useState(() => localStorage.getItem(TOKEN_KEY) || '')
|
||||
const [username, setUsername] = useState(() => localStorage.getItem(USER_KEY) || '')
|
||||
const login = async (u: string, p: string) => {
|
||||
const data = await authLogin(u, p)
|
||||
setSession(data.token, data.username)
|
||||
@@ -20,10 +20,25 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
setUsername(data.username)
|
||||
}
|
||||
const logout = () => {
|
||||
// 先吊销服务端令牌(fire-and-forget,失败不阻塞退出),再清理本地
|
||||
authLogout().catch(() => {})
|
||||
clearSession()
|
||||
setToken('')
|
||||
setUsername('')
|
||||
}
|
||||
|
||||
// 任何请求命中 401(登录态无效/过期)时,同步清空内存态,
|
||||
// AppLayout 会立即自动跳转到登录页。
|
||||
useEffect(() => {
|
||||
const onExpired = () => {
|
||||
clearSession()
|
||||
setToken('')
|
||||
setUsername('')
|
||||
}
|
||||
window.addEventListener(AUTH_EXPIRED_EVENT, onExpired)
|
||||
return () => window.removeEventListener(AUTH_EXPIRED_EVENT, onExpired)
|
||||
}, [])
|
||||
|
||||
return <Ctx.Provider value={{ token, username, login, logout }}>{children}</Ctx.Provider>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user