fix(login): frontend web auth error handle
This commit is contained in:
parent
0809b4d416
commit
140acd5852
4 changed files with 71 additions and 34 deletions
|
|
@ -49,10 +49,12 @@ export function AppHeader() {
|
|||
state: gwState,
|
||||
loading: gwLoading,
|
||||
canStart,
|
||||
startReason,
|
||||
restartRequired,
|
||||
start,
|
||||
restart,
|
||||
stop,
|
||||
error: gwError,
|
||||
} = useGateway()
|
||||
|
||||
const isRunning = gwState === "running"
|
||||
|
|
@ -196,9 +198,16 @@ export function AppHeader() {
|
|||
<IconPower className="h-4 w-4 opacity-80" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("header.gateway.action.stop")}</TooltipContent>
|
||||
<TooltipContent>{gwError ?? t("header.gateway.action.stop")}</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip delayDuration={(gwError || (!canStart && startReason)) ? 0 : 700}>
|
||||
<TooltipTrigger asChild>
|
||||
{/* Wrap in span so the tooltip still fires when the button is disabled */}
|
||||
<span
|
||||
className={!canStart && startReason ? "cursor-not-allowed" : undefined}
|
||||
tabIndex={!canStart && startReason ? 0 : undefined}
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
isStarting || isRestarting || isStopping ? "secondary" : "default"
|
||||
|
|
@ -206,7 +215,7 @@ export function AppHeader() {
|
|||
size="sm"
|
||||
data-tour="gateway-button"
|
||||
className={`h-8 gap-2 px-3 ${isStopped ? "bg-green-500 text-white hover:bg-green-600" : ""
|
||||
}`}
|
||||
} ${!canStart ? "pointer-events-none" : ""}`}
|
||||
onClick={handleGatewayToggle}
|
||||
disabled={
|
||||
gwLoading || isStarting || isRestarting || isStopping || !canStart
|
||||
|
|
@ -227,6 +236,12 @@ export function AppHeader() {
|
|||
: t("header.gateway.action.start")}
|
||||
</span>
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
{(gwError || (!canStart && startReason)) ? (
|
||||
<TooltipContent>{gwError ?? startReason}</TooltipContent>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Separator
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ import {
|
|||
|
||||
export function useGateway() {
|
||||
const gateway = useAtomValue(gatewayAtom)
|
||||
const { status: state, canStart, restartRequired } = gateway
|
||||
const { status: state, canStart, startReason, restartRequired } = gateway
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
return subscribeGatewayPolling()
|
||||
|
|
@ -23,6 +24,7 @@ export function useGateway() {
|
|||
const start = useCallback(async () => {
|
||||
if (!canStart) return
|
||||
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
try {
|
||||
await startGateway()
|
||||
|
|
@ -32,6 +34,7 @@ export function useGateway() {
|
|||
})
|
||||
} catch (err) {
|
||||
console.error("Failed to start gateway:", err)
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
} finally {
|
||||
await refreshGatewayState({ force: true })
|
||||
setLoading(false)
|
||||
|
|
@ -39,12 +42,14 @@ export function useGateway() {
|
|||
}, [canStart])
|
||||
|
||||
const stop = useCallback(async () => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
beginGatewayStoppingTransition()
|
||||
try {
|
||||
await stopGateway()
|
||||
} catch (err) {
|
||||
console.error("Failed to stop gateway:", err)
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
cancelGatewayStoppingTransition()
|
||||
} finally {
|
||||
await refreshGatewayState({ force: true })
|
||||
|
|
@ -55,6 +60,7 @@ export function useGateway() {
|
|||
const restart = useCallback(async () => {
|
||||
if (state !== "running") return
|
||||
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
try {
|
||||
await restartGateway()
|
||||
|
|
@ -64,11 +70,12 @@ export function useGateway() {
|
|||
})
|
||||
} catch (err) {
|
||||
console.error("Failed to restart gateway:", err)
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
} finally {
|
||||
await refreshGatewayState({ force: true })
|
||||
setLoading(false)
|
||||
}
|
||||
}, [state])
|
||||
|
||||
return { state, loading, canStart, restartRequired, start, stop, restart }
|
||||
return { state, loading, canStart, startReason, restartRequired, start, stop, restart, error }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,14 @@ const RootLayout = () => {
|
|||
globalThis.location.assign("/launcher-login")
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Network error or 401 — launcherFetch will handle redirect on real API calls.
|
||||
.catch((err: unknown) => {
|
||||
// For real HTTP errors (e.g. 503 when the auth store is unavailable),
|
||||
// redirect to login so the user can re-authenticate rather than being
|
||||
// silently stranded on the dashboard. Network failures (no response)
|
||||
// are left alone — launcherFetch handles the 401 redirect on real calls.
|
||||
if (err instanceof Error && /^status \d+$/.test(err.message)) {
|
||||
globalThis.location.assign("/launcher-login")
|
||||
}
|
||||
})
|
||||
}, [isAuthPage])
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export type GatewayState =
|
|||
export interface GatewayStoreState {
|
||||
status: GatewayState
|
||||
canStart: boolean
|
||||
startReason?: string
|
||||
restartRequired: boolean
|
||||
}
|
||||
|
||||
|
|
@ -57,6 +58,7 @@ function normalizeGatewayStoreState(
|
|||
if (
|
||||
next.status === prev.status &&
|
||||
next.canStart === prev.canStart &&
|
||||
next.startReason === prev.startReason &&
|
||||
next.restartRequired === prev.restartRequired
|
||||
) {
|
||||
return prev
|
||||
|
|
@ -108,7 +110,10 @@ export function applyGatewayStatusToStore(
|
|||
data: Partial<
|
||||
Pick<
|
||||
GatewayStatusResponse,
|
||||
"gateway_status" | "gateway_start_allowed" | "gateway_restart_required"
|
||||
| "gateway_status"
|
||||
| "gateway_start_allowed"
|
||||
| "gateway_start_reason"
|
||||
| "gateway_restart_required"
|
||||
>
|
||||
>,
|
||||
) {
|
||||
|
|
@ -121,6 +126,10 @@ export function applyGatewayStatusToStore(
|
|||
prev.status === "stopping" && data.gateway_status === "running"
|
||||
? false
|
||||
: (data.gateway_start_allowed ?? prev.canStart),
|
||||
startReason:
|
||||
prev.status === "stopping" && data.gateway_status === "running"
|
||||
? prev.startReason
|
||||
: (data.gateway_start_reason ?? prev.startReason),
|
||||
restartRequired:
|
||||
prev.status === "stopping" && data.gateway_status === "running"
|
||||
? false
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue