mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-03 01:06:36 +08:00
feat(frontend): implement AJAX no-refresh UX improvements (#276)
- Add dashboard auto-refresh with 5s polling and visibility-aware pause - Harden node realtime reconnection with exponential backoff and polling fallback - Implement local state patching for forward/tunnel/user mutations - Add batch operation progress feedback UI - Add shared list-state helpers for replace/remove operations - Preserve derived UI state during server payload merges Closes #276 Entire-Checkpoint: 7d6188355d7f
This commit is contained in:
@@ -15,6 +15,11 @@ interface UseNodeRealtimeOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
const MAX_STANDARD_RECONNECT_ATTEMPTS = 5;
|
||||
const STANDARD_RECONNECT_DELAY_MS = 3000;
|
||||
const MAX_STANDARD_RECONNECT_DELAY_MS = 15000;
|
||||
const FALLBACK_RECONNECT_DELAY_MS = 30000;
|
||||
|
||||
const getRealtimeWsUrl = (): string => {
|
||||
const baseUrl =
|
||||
axios.defaults.baseURL ||
|
||||
@@ -34,14 +39,13 @@ export const useNodeRealtime = ({
|
||||
}: UseNodeRealtimeOptions) => {
|
||||
const [wsConnected, setWsConnected] = useState(false);
|
||||
const [wsConnecting, setWsConnecting] = useState(false);
|
||||
const [usingPollingFallback, setUsingPollingFallback] = useState(false);
|
||||
|
||||
const websocketRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const reconnectAttemptsRef = useRef(0);
|
||||
const onMessageRef = useRef(onMessage);
|
||||
|
||||
const maxReconnectAttempts = 5;
|
||||
|
||||
useEffect(() => {
|
||||
onMessageRef.current = onMessage;
|
||||
}, [onMessage]);
|
||||
@@ -58,6 +62,7 @@ export const useNodeRealtime = ({
|
||||
reconnectAttemptsRef.current = 0;
|
||||
setWsConnected(false);
|
||||
setWsConnecting(false);
|
||||
setUsingPollingFallback(false);
|
||||
|
||||
if (!websocketRef.current) {
|
||||
return;
|
||||
@@ -103,6 +108,7 @@ export const useNodeRealtime = ({
|
||||
reconnectAttemptsRef.current = 0;
|
||||
setWsConnected(true);
|
||||
setWsConnecting(false);
|
||||
setUsingPollingFallback(false);
|
||||
};
|
||||
|
||||
websocketRef.current.onmessage = (event) => {
|
||||
@@ -122,15 +128,27 @@ export const useNodeRealtime = ({
|
||||
setWsConnected(false);
|
||||
setWsConnecting(false);
|
||||
|
||||
if (!enabled || reconnectAttemptsRef.current >= maxReconnectAttempts) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
reconnectAttemptsRef.current += 1;
|
||||
const exhaustedStandardRetries =
|
||||
reconnectAttemptsRef.current >= MAX_STANDARD_RECONNECT_ATTEMPTS;
|
||||
|
||||
setUsingPollingFallback(exhaustedStandardRetries);
|
||||
|
||||
const reconnectDelay = exhaustedStandardRetries
|
||||
? FALLBACK_RECONNECT_DELAY_MS
|
||||
: Math.min(
|
||||
STANDARD_RECONNECT_DELAY_MS * reconnectAttemptsRef.current,
|
||||
MAX_STANDARD_RECONNECT_DELAY_MS,
|
||||
);
|
||||
|
||||
reconnectTimerRef.current = setTimeout(() => {
|
||||
reconnectTimerRef.current = null;
|
||||
connect();
|
||||
}, 3000 * reconnectAttemptsRef.current);
|
||||
}, reconnectDelay);
|
||||
};
|
||||
} catch {
|
||||
setWsConnected(false);
|
||||
@@ -153,6 +171,7 @@ export const useNodeRealtime = ({
|
||||
return {
|
||||
wsConnected,
|
||||
wsConnecting,
|
||||
usingPollingFallback,
|
||||
reconnectRealtime: connect,
|
||||
disconnectRealtime: disconnect,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user