|
|
import { useState, useEffect, useRef, useCallback } from 'react'; |
|
|
import { useTranslation } from 'react-i18next'; |
|
|
import { Server, ApiResponse } from '@/types'; |
|
|
import { getApiUrl } from '../utils/runtime'; |
|
|
|
|
|
|
|
|
const CONFIG = { |
|
|
|
|
|
startup: { |
|
|
maxAttempts: 60, |
|
|
pollingInterval: 3000, |
|
|
}, |
|
|
|
|
|
normal: { |
|
|
pollingInterval: 10000, |
|
|
}, |
|
|
}; |
|
|
|
|
|
export const useServerData = () => { |
|
|
const { t } = useTranslation(); |
|
|
const [servers, setServers] = useState<Server[]>([]); |
|
|
const [error, setError] = useState<string | null>(null); |
|
|
const [refreshKey, setRefreshKey] = useState(0); |
|
|
const [isInitialLoading, setIsInitialLoading] = useState(true); |
|
|
const [fetchAttempts, setFetchAttempts] = useState(0); |
|
|
|
|
|
|
|
|
const intervalRef = useRef<NodeJS.Timeout | null>(null); |
|
|
|
|
|
const attemptsRef = useRef<number>(0); |
|
|
|
|
|
|
|
|
const clearTimer = () => { |
|
|
if (intervalRef.current) { |
|
|
clearInterval(intervalRef.current); |
|
|
intervalRef.current = null; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const startNormalPolling = useCallback(() => { |
|
|
|
|
|
clearTimer(); |
|
|
|
|
|
const fetchServers = async () => { |
|
|
try { |
|
|
const token = localStorage.getItem('mcphub_token'); |
|
|
const response = await fetch(getApiUrl('/servers'), { |
|
|
headers: { |
|
|
'x-auth-token': token || '', |
|
|
}, |
|
|
}); |
|
|
const data = await response.json(); |
|
|
|
|
|
if (data && data.success && Array.isArray(data.data)) { |
|
|
setServers(data.data); |
|
|
} else if (data && Array.isArray(data)) { |
|
|
setServers(data); |
|
|
} else { |
|
|
console.error('Invalid server data format:', data); |
|
|
setServers([]); |
|
|
} |
|
|
|
|
|
|
|
|
setError(null); |
|
|
} catch (err) { |
|
|
console.error('Error fetching servers during normal polling:', err); |
|
|
|
|
|
|
|
|
if (!navigator.onLine) { |
|
|
setError(t('errors.network')); |
|
|
} else if ( |
|
|
err instanceof TypeError && |
|
|
(err.message.includes('NetworkError') || err.message.includes('Failed to fetch')) |
|
|
) { |
|
|
setError(t('errors.serverConnection')); |
|
|
} else { |
|
|
setError(t('errors.serverFetch')); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
fetchServers(); |
|
|
|
|
|
|
|
|
intervalRef.current = setInterval(fetchServers, CONFIG.normal.pollingInterval); |
|
|
}, [t]); |
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
if (refreshKey > 0) { |
|
|
attemptsRef.current = 0; |
|
|
setFetchAttempts(0); |
|
|
} |
|
|
|
|
|
|
|
|
const fetchInitialData = async () => { |
|
|
try { |
|
|
const token = localStorage.getItem('mcphub_token'); |
|
|
const response = await fetch(getApiUrl('/servers'), { |
|
|
headers: { |
|
|
'x-auth-token': token || '', |
|
|
}, |
|
|
}); |
|
|
const data = await response.json(); |
|
|
|
|
|
|
|
|
if (data && data.success && Array.isArray(data.data)) { |
|
|
setServers(data.data); |
|
|
setIsInitialLoading(false); |
|
|
|
|
|
startNormalPolling(); |
|
|
return true; |
|
|
} else if (data && Array.isArray(data)) { |
|
|
|
|
|
setServers(data); |
|
|
setIsInitialLoading(false); |
|
|
|
|
|
startNormalPolling(); |
|
|
return true; |
|
|
} else { |
|
|
|
|
|
console.error('Invalid server data format:', data); |
|
|
setServers([]); |
|
|
setIsInitialLoading(false); |
|
|
|
|
|
startNormalPolling(); |
|
|
return true; |
|
|
} |
|
|
} catch (err) { |
|
|
|
|
|
attemptsRef.current += 1; |
|
|
console.error(`Initial loading attempt ${attemptsRef.current} failed:`, err); |
|
|
|
|
|
|
|
|
setFetchAttempts(attemptsRef.current); |
|
|
|
|
|
|
|
|
if (!navigator.onLine) { |
|
|
setError(t('errors.network')); |
|
|
} else { |
|
|
setError(t('errors.initialStartup')); |
|
|
} |
|
|
|
|
|
|
|
|
if (attemptsRef.current >= CONFIG.startup.maxAttempts) { |
|
|
console.log('Maximum startup attempts reached, switching to normal polling'); |
|
|
setIsInitialLoading(false); |
|
|
|
|
|
clearTimer(); |
|
|
|
|
|
startNormalPolling(); |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
if (isInitialLoading) { |
|
|
|
|
|
clearTimer(); |
|
|
|
|
|
|
|
|
fetchInitialData(); |
|
|
|
|
|
|
|
|
intervalRef.current = setInterval(fetchInitialData, CONFIG.startup.pollingInterval); |
|
|
console.log(`Started initial polling with interval: ${CONFIG.startup.pollingInterval}ms`); |
|
|
} else { |
|
|
|
|
|
startNormalPolling(); |
|
|
} |
|
|
|
|
|
|
|
|
return () => { |
|
|
clearTimer(); |
|
|
}; |
|
|
}, [refreshKey, t, isInitialLoading, startNormalPolling]); |
|
|
|
|
|
|
|
|
const triggerRefresh = () => { |
|
|
|
|
|
clearTimer(); |
|
|
|
|
|
|
|
|
if (isInitialLoading) { |
|
|
setIsInitialLoading(true); |
|
|
attemptsRef.current = 0; |
|
|
setFetchAttempts(0); |
|
|
} |
|
|
|
|
|
|
|
|
setRefreshKey((prevKey) => prevKey + 1); |
|
|
}; |
|
|
|
|
|
|
|
|
const handleServerAdd = () => { |
|
|
setRefreshKey((prevKey) => prevKey + 1); |
|
|
}; |
|
|
|
|
|
const handleServerEdit = async (server: Server) => { |
|
|
try { |
|
|
|
|
|
const token = localStorage.getItem('mcphub_token'); |
|
|
const response = await fetch(getApiUrl('/settings'), { |
|
|
headers: { |
|
|
'x-auth-token': token || '', |
|
|
}, |
|
|
}); |
|
|
|
|
|
const settingsData: ApiResponse<{ mcpServers: Record<string, any> }> = await response.json(); |
|
|
|
|
|
if ( |
|
|
settingsData && |
|
|
settingsData.success && |
|
|
settingsData.data && |
|
|
settingsData.data.mcpServers && |
|
|
settingsData.data.mcpServers[server.name] |
|
|
) { |
|
|
const serverConfig = settingsData.data.mcpServers[server.name]; |
|
|
return { |
|
|
name: server.name, |
|
|
status: server.status, |
|
|
tools: server.tools || [], |
|
|
config: serverConfig, |
|
|
}; |
|
|
} else { |
|
|
console.error('Failed to get server config from settings:', settingsData); |
|
|
setError(t('server.invalidConfig', { serverName: server.name })); |
|
|
return null; |
|
|
} |
|
|
} catch (err) { |
|
|
console.error('Error fetching server settings:', err); |
|
|
setError(err instanceof Error ? err.message : String(err)); |
|
|
return null; |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleServerRemove = async (serverName: string) => { |
|
|
try { |
|
|
const token = localStorage.getItem('mcphub_token'); |
|
|
const response = await fetch(getApiUrl(`/servers/${serverName}`), { |
|
|
method: 'DELETE', |
|
|
headers: { |
|
|
'x-auth-token': token || '', |
|
|
}, |
|
|
}); |
|
|
const result = await response.json(); |
|
|
|
|
|
if (!response.ok) { |
|
|
setError(result.message || t('server.deleteError', { serverName })); |
|
|
return false; |
|
|
} |
|
|
|
|
|
setRefreshKey((prevKey) => prevKey + 1); |
|
|
return true; |
|
|
} catch (err) { |
|
|
setError(t('errors.general') + ': ' + (err instanceof Error ? err.message : String(err))); |
|
|
return false; |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleServerToggle = async (server: Server, enabled: boolean) => { |
|
|
try { |
|
|
const token = localStorage.getItem('mcphub_token'); |
|
|
const response = await fetch(getApiUrl(`/servers/${server.name}/toggle`), { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
'x-auth-token': token || '', |
|
|
}, |
|
|
body: JSON.stringify({ enabled }), |
|
|
}); |
|
|
|
|
|
const result = await response.json(); |
|
|
|
|
|
if (!response.ok) { |
|
|
console.error('Failed to toggle server:', result); |
|
|
setError(t('server.toggleError', { serverName: server.name })); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
setRefreshKey((prevKey) => prevKey + 1); |
|
|
return true; |
|
|
} catch (err) { |
|
|
console.error('Error toggling server:', err); |
|
|
setError(err instanceof Error ? err.message : String(err)); |
|
|
return false; |
|
|
} |
|
|
}; |
|
|
|
|
|
return { |
|
|
servers, |
|
|
error, |
|
|
setError, |
|
|
isLoading: isInitialLoading, |
|
|
fetchAttempts, |
|
|
triggerRefresh, |
|
|
handleServerAdd, |
|
|
handleServerEdit, |
|
|
handleServerRemove, |
|
|
handleServerToggle, |
|
|
}; |
|
|
}; |
|
|
|