feat: Refactor API URL handling and add base path support (#131)

This commit is contained in:
samanhappy
2025-05-27 16:11:35 +08:00
committed by GitHub
parent 37bb3414c8
commit 268ce5cce6
18 changed files with 624 additions and 443 deletions

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Group, ApiResponse } from '@/types';
import { getApiUrl } from '../utils/api';
export const useGroupData = () => {
const { t } = useTranslation();
@@ -13,25 +14,25 @@ export const useGroupData = () => {
try {
setLoading(true);
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/groups', {
const response = await fetch(getApiUrl('/groups'), {
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data: ApiResponse<Group[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setGroups(data.data);
} else {
console.error('Invalid group data format:', data);
setGroups([]);
}
setError(null);
} catch (err) {
console.error('Error fetching groups:', err);
@@ -44,29 +45,29 @@ export const useGroupData = () => {
// Trigger a refresh of the groups data
const triggerRefresh = useCallback(() => {
setRefreshKey(prev => prev + 1);
setRefreshKey((prev) => prev + 1);
}, []);
// Create a new group with server associations
const createGroup = async (name: string, description?: string, servers: string[] = []) => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/groups', {
const response = await fetch(getApiUrl('/groups'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || ''
'x-auth-token': token || '',
},
body: JSON.stringify({ name, description, servers }),
});
const result: ApiResponse<Group> = await response.json();
if (!response.ok) {
setError(result.message || t('groups.createError'));
return null;
}
triggerRefresh();
return result.data || null;
} catch (err) {
@@ -76,25 +77,28 @@ export const useGroupData = () => {
};
// Update an existing group with server associations
const updateGroup = async (id: string, data: { name?: string; description?: string; servers?: string[] }) => {
const updateGroup = async (
id: string,
data: { name?: string; description?: string; servers?: string[] },
) => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/groups/${id}`, {
const response = await fetch(getApiUrl(`/groups/${id}`), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || ''
'x-auth-token': token || '',
},
body: JSON.stringify(data),
});
const result: ApiResponse<Group> = await response.json();
if (!response.ok) {
setError(result.message || t('groups.updateError'));
return null;
}
triggerRefresh();
return result.data || null;
} catch (err) {
@@ -107,22 +111,22 @@ export const useGroupData = () => {
const updateGroupServers = async (groupId: string, servers: string[]) => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/groups/${groupId}/servers/batch`, {
const response = await fetch(getApiUrl(`/groups/${groupId}/servers/batch`), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || ''
'x-auth-token': token || '',
},
body: JSON.stringify({ servers }),
});
const result: ApiResponse<Group> = await response.json();
if (!response.ok) {
setError(result.message || t('groups.updateError'));
return null;
}
triggerRefresh();
return result.data || null;
} catch (err) {
@@ -135,20 +139,20 @@ export const useGroupData = () => {
const deleteGroup = async (id: string) => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/groups/${id}`, {
const response = await fetch(getApiUrl(`/groups/${id}`), {
method: 'DELETE',
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
const result = await response.json();
if (!response.ok) {
setError(result.message || t('groups.deleteError'));
return false;
}
triggerRefresh();
return true;
} catch (err) {
@@ -161,22 +165,22 @@ export const useGroupData = () => {
const addServerToGroup = async (groupId: string, serverName: string) => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/groups/${groupId}/servers`, {
const response = await fetch(getApiUrl(`/groups/${groupId}/servers`), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || ''
'x-auth-token': token || '',
},
body: JSON.stringify({ serverName }),
});
const result: ApiResponse<Group> = await response.json();
if (!response.ok) {
setError(result.message || t('groups.serverAddError'));
return null;
}
triggerRefresh();
return result.data || null;
} catch (err) {
@@ -189,20 +193,20 @@ export const useGroupData = () => {
const removeServerFromGroup = async (groupId: string, serverName: string) => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/groups/${groupId}/servers/${serverName}`, {
const response = await fetch(getApiUrl(`/groups/${groupId}/servers/${serverName}`), {
method: 'DELETE',
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
const result: ApiResponse<Group> = await response.json();
if (!response.ok) {
setError(result.message || t('groups.serverRemoveError'));
return null;
}
triggerRefresh();
return result.data || null;
} catch (err) {
@@ -227,6 +231,6 @@ export const useGroupData = () => {
updateGroupServers,
deleteGroup,
addServerToGroup,
removeServerFromGroup
removeServerFromGroup,
};
};
};

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { MarketServer, ApiResponse } from '@/types';
import { getApiUrl } from '../utils/api';
export const useMarketData = () => {
const { t } = useTranslation();
@@ -15,7 +16,7 @@ export const useMarketData = () => {
const [error, setError] = useState<string | null>(null);
const [currentServer, setCurrentServer] = useState<MarketServer | null>(null);
const [installedServers, setInstalledServers] = useState<string[]>([]);
// Pagination states
const [currentPage, setCurrentPage] = useState(1);
const [serversPerPage, setServersPerPage] = useState(9);
@@ -26,18 +27,18 @@ export const useMarketData = () => {
try {
setLoading(true);
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/market/servers', {
const response = await fetch(getApiUrl('/market/servers'), {
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data: ApiResponse<MarketServer[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setAllServers(data.data);
// Apply pagination to the fetched data
@@ -55,44 +56,50 @@ export const useMarketData = () => {
}, [t]);
// Apply pagination to data
const applyPagination = useCallback((data: MarketServer[], page: number, itemsPerPage = serversPerPage) => {
const totalItems = data.length;
const calculatedTotalPages = Math.ceil(totalItems / itemsPerPage);
setTotalPages(calculatedTotalPages);
const applyPagination = useCallback(
(data: MarketServer[], page: number, itemsPerPage = serversPerPage) => {
const totalItems = data.length;
const calculatedTotalPages = Math.ceil(totalItems / itemsPerPage);
setTotalPages(calculatedTotalPages);
// Ensure current page is valid
const validPage = Math.max(1, Math.min(page, calculatedTotalPages));
if (validPage !== page) {
setCurrentPage(validPage);
}
// Ensure current page is valid
const validPage = Math.max(1, Math.min(page, calculatedTotalPages));
if (validPage !== page) {
setCurrentPage(validPage);
}
const startIndex = (validPage - 1) * itemsPerPage;
const paginatedServers = data.slice(startIndex, startIndex + itemsPerPage);
setServers(paginatedServers);
}, [serversPerPage]);
const startIndex = (validPage - 1) * itemsPerPage;
const paginatedServers = data.slice(startIndex, startIndex + itemsPerPage);
setServers(paginatedServers);
},
[serversPerPage],
);
// Change page
const changePage = useCallback((page: number) => {
setCurrentPage(page);
applyPagination(allServers, page, serversPerPage);
}, [allServers, applyPagination, serversPerPage]);
const changePage = useCallback(
(page: number) => {
setCurrentPage(page);
applyPagination(allServers, page, serversPerPage);
},
[allServers, applyPagination, serversPerPage],
);
// Fetch all categories
const fetchCategories = useCallback(async () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/market/categories', {
const response = await fetch(getApiUrl('/market/categories'), {
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data: ApiResponse<string[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setCategories(data.data);
} else {
@@ -107,18 +114,18 @@ export const useMarketData = () => {
const fetchTags = useCallback(async () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/market/tags', {
const response = await fetch(getApiUrl('/market/tags'), {
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data: ApiResponse<string[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setTags(data.data);
} else {
@@ -130,178 +137,196 @@ export const useMarketData = () => {
}, []);
// Fetch server by name
const fetchServerByName = useCallback(async (name: string) => {
try {
setLoading(true);
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/market/servers/${name}`, {
headers: {
'x-auth-token': token || ''
const fetchServerByName = useCallback(
async (name: string) => {
try {
setLoading(true);
const token = localStorage.getItem('mcphub_token');
const response = await fetch(getApiUrl(`/market/servers/${name}`), {
headers: {
'x-auth-token': token || '',
},
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data: ApiResponse<MarketServer> = await response.json();
if (data && data.success && data.data) {
setCurrentServer(data.data);
return data.data;
} else {
console.error('Invalid server data format:', data);
setError(t('market.serverNotFound'));
const data: ApiResponse<MarketServer> = await response.json();
if (data && data.success && data.data) {
setCurrentServer(data.data);
return data.data;
} else {
console.error('Invalid server data format:', data);
setError(t('market.serverNotFound'));
return null;
}
} catch (err) {
console.error(`Error fetching server ${name}:`, err);
setError(err instanceof Error ? err.message : String(err));
return null;
} finally {
setLoading(false);
}
} catch (err) {
console.error(`Error fetching server ${name}:`, err);
setError(err instanceof Error ? err.message : String(err));
return null;
} finally {
setLoading(false);
}
}, [t]);
},
[t],
);
// Search servers by query
const searchServers = useCallback(async (query: string) => {
try {
setLoading(true);
setSearchQuery(query);
if (!query.trim()) {
// Fetch fresh data from server instead of just applying pagination
fetchMarketServers();
return;
}
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/market/servers/search?query=${encodeURIComponent(query)}`, {
headers: {
'x-auth-token': token || ''
const searchServers = useCallback(
async (query: string) => {
try {
setLoading(true);
setSearchQuery(query);
if (!query.trim()) {
// Fetch fresh data from server instead of just applying pagination
fetchMarketServers();
return;
}
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
const token = localStorage.getItem('mcphub_token');
const response = await fetch(
getApiUrl(`/market/servers/search?query=${encodeURIComponent(query)}`),
{
headers: {
'x-auth-token': token || '',
},
},
);
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data: ApiResponse<MarketServer[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setAllServers(data.data);
setCurrentPage(1);
applyPagination(data.data, 1);
} else {
console.error('Invalid search results format:', data);
setError(t('market.searchError'));
}
} catch (err) {
console.error('Error searching servers:', err);
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
const data: ApiResponse<MarketServer[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setAllServers(data.data);
setCurrentPage(1);
applyPagination(data.data, 1);
} else {
console.error('Invalid search results format:', data);
setError(t('market.searchError'));
}
} catch (err) {
console.error('Error searching servers:', err);
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, [t, allServers, applyPagination, fetchMarketServers]);
},
[t, allServers, applyPagination, fetchMarketServers],
);
// Filter servers by category
const filterByCategory = useCallback(async (category: string) => {
try {
setLoading(true);
setSelectedCategory(category);
setSelectedTag(''); // Reset tag filter when filtering by category
if (!category) {
fetchMarketServers();
return;
}
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/market/categories/${encodeURIComponent(category)}`, {
headers: {
'x-auth-token': token || ''
const filterByCategory = useCallback(
async (category: string) => {
try {
setLoading(true);
setSelectedCategory(category);
setSelectedTag(''); // Reset tag filter when filtering by category
if (!category) {
fetchMarketServers();
return;
}
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
const token = localStorage.getItem('mcphub_token');
const response = await fetch(
getApiUrl(`/market/categories/${encodeURIComponent(category)}`),
{
headers: {
'x-auth-token': token || '',
},
},
);
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data: ApiResponse<MarketServer[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setAllServers(data.data);
setCurrentPage(1);
applyPagination(data.data, 1);
} else {
console.error('Invalid category filter results format:', data);
setError(t('market.filterError'));
}
} catch (err) {
console.error('Error filtering servers by category:', err);
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
const data: ApiResponse<MarketServer[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setAllServers(data.data);
setCurrentPage(1);
applyPagination(data.data, 1);
} else {
console.error('Invalid category filter results format:', data);
setError(t('market.filterError'));
}
} catch (err) {
console.error('Error filtering servers by category:', err);
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, [t, fetchMarketServers, applyPagination]);
},
[t, fetchMarketServers, applyPagination],
);
// Filter servers by tag
const filterByTag = useCallback(async (tag: string) => {
try {
setLoading(true);
setSelectedTag(tag);
setSelectedCategory(''); // Reset category filter when filtering by tag
if (!tag) {
fetchMarketServers();
return;
}
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/market/tags/${encodeURIComponent(tag)}`, {
headers: {
'x-auth-token': token || ''
const filterByTag = useCallback(
async (tag: string) => {
try {
setLoading(true);
setSelectedTag(tag);
setSelectedCategory(''); // Reset category filter when filtering by tag
if (!tag) {
fetchMarketServers();
return;
}
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
const token = localStorage.getItem('mcphub_token');
const response = await fetch(getApiUrl(`/market/tags/${encodeURIComponent(tag)}`), {
headers: {
'x-auth-token': token || '',
},
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data: ApiResponse<MarketServer[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setAllServers(data.data);
setCurrentPage(1);
applyPagination(data.data, 1);
} else {
console.error('Invalid tag filter results format:', data);
setError(t('market.tagFilterError'));
}
} catch (err) {
console.error('Error filtering servers by tag:', err);
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
const data: ApiResponse<MarketServer[]> = await response.json();
if (data && data.success && Array.isArray(data.data)) {
setAllServers(data.data);
setCurrentPage(1);
applyPagination(data.data, 1);
} else {
console.error('Invalid tag filter results format:', data);
setError(t('market.tagFilterError'));
}
} catch (err) {
console.error('Error filtering servers by tag:', err);
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, [t, fetchMarketServers, applyPagination]);
},
[t, fetchMarketServers, applyPagination],
);
// Fetch installed servers
const fetchInstalledServers = useCallback(async () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/servers', {
const response = await fetch(getApiUrl('/servers'), {
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
if (!response.ok) {
throw new Error(`Status: ${response.status}`);
}
const data = await response.json();
if (data && data.success && Array.isArray(data.data)) {
// Extract server names
const installedServerNames = data.data.map((server: any) => server.name);
@@ -313,64 +338,77 @@ export const useMarketData = () => {
}, []);
// Check if a server is already installed
const isServerInstalled = useCallback((serverName: string) => {
return installedServers.includes(serverName);
}, [installedServers]);
const isServerInstalled = useCallback(
(serverName: string) => {
return installedServers.includes(serverName);
},
[installedServers],
);
// Install server to the local environment
const installServer = useCallback(async (server: MarketServer) => {
try {
const installType = server.installations?.npm ? 'npm' : Object.keys(server.installations || {}).length > 0 ? Object.keys(server.installations)[0] : null;
if (!installType || !server.installations?.[installType]) {
setError(t('market.noInstallationMethod'));
const installServer = useCallback(
async (server: MarketServer) => {
try {
const installType = server.installations?.npm
? 'npm'
: Object.keys(server.installations || {}).length > 0
? Object.keys(server.installations)[0]
: null;
if (!installType || !server.installations?.[installType]) {
setError(t('market.noInstallationMethod'));
return false;
}
const installation = server.installations[installType];
// Prepare server configuration
const serverConfig = {
name: server.name,
config: {
command: installation.command,
args: installation.args,
env: installation.env || {},
},
};
// Call the createServer API
const token = localStorage.getItem('mcphub_token');
const response = await fetch(getApiUrl('/servers'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || '',
},
body: JSON.stringify(serverConfig),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `Status: ${response.status}`);
}
// Update installed servers list after successful installation
await fetchInstalledServers();
return true;
} catch (err) {
console.error('Error installing server:', err);
setError(err instanceof Error ? err.message : String(err));
return false;
}
const installation = server.installations[installType];
// Prepare server configuration
const serverConfig = {
name: server.name,
config: {
command: installation.command,
args: installation.args,
env: installation.env || {}
}
};
// Call the createServer API
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/servers', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || ''
},
body: JSON.stringify(serverConfig),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `Status: ${response.status}`);
}
// Update installed servers list after successful installation
await fetchInstalledServers();
return true;
} catch (err) {
console.error('Error installing server:', err);
setError(err instanceof Error ? err.message : String(err));
return false;
}
}, [t, fetchInstalledServers]);
},
[t, fetchInstalledServers],
);
// Change servers per page
const changeServersPerPage = useCallback((perPage: number) => {
setServersPerPage(perPage);
setCurrentPage(1);
applyPagination(allServers, 1, perPage);
}, [allServers, applyPagination]);
const changeServersPerPage = useCallback(
(perPage: number) => {
setServersPerPage(perPage);
setCurrentPage(1);
applyPagination(allServers, 1, perPage);
},
[allServers, applyPagination],
);
// Load initial data
useEffect(() => {
@@ -405,6 +443,6 @@ export const useMarketData = () => {
changePage,
changeServersPerPage,
// Installed servers methods
isServerInstalled
isServerInstalled,
};
};
};

View File

@@ -1,18 +1,19 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Server, ApiResponse } from '@/types';
import { getApiUrl } from '../utils/api';
// Configuration options
const CONFIG = {
// Initialization phase configuration
startup: {
maxAttempts: 60, // Maximum number of attempts during initialization
pollingInterval: 3000 // Polling interval during initialization (3 seconds)
maxAttempts: 60, // Maximum number of attempts during initialization
pollingInterval: 3000, // Polling interval during initialization (3 seconds)
},
// Normal operation phase configuration
normal: {
pollingInterval: 10000 // Polling interval during normal operation (10 seconds)
}
pollingInterval: 10000, // Polling interval during normal operation (10 seconds)
},
};
export const useServerData = () => {
@@ -22,7 +23,7 @@ export const useServerData = () => {
const [refreshKey, setRefreshKey] = useState(0);
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [fetchAttempts, setFetchAttempts] = useState(0);
// Timer reference for polling
const intervalRef = useRef<NodeJS.Timeout | null>(null);
// Track current attempt count to avoid dependency cycles
@@ -40,17 +41,17 @@ export const useServerData = () => {
const startNormalPolling = useCallback(() => {
// Ensure no other timers are running
clearTimer();
const fetchServers = async () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/servers', {
const response = await fetch(getApiUrl('/servers'), {
headers: {
'x-auth-token': token || ''
}
'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)) {
@@ -59,29 +60,29 @@ export const useServerData = () => {
console.error('Invalid server data format:', data);
setServers([]);
}
// Reset error state
setError(null);
} catch (err) {
console.error('Error fetching servers during normal polling:', err);
// Use friendly error message
if (!navigator.onLine) {
setError(t('errors.network'));
} else if (err instanceof TypeError && (
err.message.includes('NetworkError') ||
err.message.includes('Failed to fetch')
)) {
} else if (
err instanceof TypeError &&
(err.message.includes('NetworkError') || err.message.includes('Failed to fetch'))
) {
setError(t('errors.serverConnection'));
} else {
setError(t('errors.serverFetch'));
}
}
};
// Execute immediately
fetchServers();
// Set up regular polling
intervalRef.current = setInterval(fetchServers, CONFIG.normal.pollingInterval);
}, [t]);
@@ -92,18 +93,18 @@ export const useServerData = () => {
attemptsRef.current = 0;
setFetchAttempts(0);
}
// Initialization phase request function
const fetchInitialData = async () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/servers', {
const response = await fetch(getApiUrl('/servers'), {
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
const data = await response.json();
// Handle API response wrapper object, extract data field
if (data && data.success && Array.isArray(data.data)) {
setServers(data.data);
@@ -131,17 +132,17 @@ export const useServerData = () => {
// Increment attempt count, use ref to avoid triggering effect rerun
attemptsRef.current += 1;
console.error(`Initial loading attempt ${attemptsRef.current} failed:`, err);
// Update state for display
setFetchAttempts(attemptsRef.current);
// Set appropriate error message
if (!navigator.onLine) {
setError(t('errors.network'));
} else {
setError(t('errors.initialStartup'));
}
// If maximum attempt count is exceeded, give up initialization and switch to normal polling
if (attemptsRef.current >= CONFIG.startup.maxAttempts) {
console.log('Maximum startup attempts reached, switching to normal polling');
@@ -151,19 +152,19 @@ export const useServerData = () => {
// Switch to normal polling mode
startNormalPolling();
}
return false;
}
};
// On component mount, set appropriate polling based on current state
if (isInitialLoading) {
// Ensure no other timers are running
clearTimer();
// Execute initial request immediately
fetchInitialData();
// Set polling interval for initialization phase
intervalRef.current = setInterval(fetchInitialData, CONFIG.startup.pollingInterval);
console.log(`Started initial polling with interval: ${CONFIG.startup.pollingInterval}ms`);
@@ -171,7 +172,7 @@ export const useServerData = () => {
// Initialization completed, start normal polling
startNormalPolling();
}
// Cleanup function
return () => {
clearTimer();
@@ -182,21 +183,21 @@ export const useServerData = () => {
const triggerRefresh = () => {
// Clear current timer
clearTimer();
// If in initialization phase, reset initialization state
if (isInitialLoading) {
setIsInitialLoading(true);
attemptsRef.current = 0;
setFetchAttempts(0);
}
// Change in refreshKey will trigger useEffect to run again
setRefreshKey(prevKey => prevKey + 1);
setRefreshKey((prevKey) => prevKey + 1);
};
// Server related operations
const handleServerAdd = () => {
setRefreshKey(prevKey => prevKey + 1);
setRefreshKey((prevKey) => prevKey + 1);
};
const handleServerEdit = async (server: Server) => {
@@ -205,12 +206,12 @@ export const useServerData = () => {
const token = localStorage.getItem('mcphub_token');
const response = await fetch(`/api/settings`, {
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
const settingsData: ApiResponse<{ mcpServers: Record<string, any> }> = await response.json();
if (
settingsData &&
settingsData.success &&
@@ -243,8 +244,8 @@ export const useServerData = () => {
const response = await fetch(`/api/servers/${serverName}`, {
method: 'DELETE',
headers: {
'x-auth-token': token || ''
}
'x-auth-token': token || '',
},
});
const result = await response.json();
@@ -253,7 +254,7 @@ export const useServerData = () => {
return false;
}
setRefreshKey(prevKey => prevKey + 1);
setRefreshKey((prevKey) => prevKey + 1);
return true;
} catch (err) {
setError(t('errors.general') + ': ' + (err instanceof Error ? err.message : String(err)));
@@ -268,7 +269,7 @@ export const useServerData = () => {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || ''
'x-auth-token': token || '',
},
body: JSON.stringify({ enabled }),
});
@@ -282,7 +283,7 @@ export const useServerData = () => {
}
// Update the UI immediately to reflect the change
setRefreshKey(prevKey => prevKey + 1);
setRefreshKey((prevKey) => prevKey + 1);
return true;
} catch (err) {
console.error('Error toggling server:', err);
@@ -301,6 +302,6 @@ export const useServerData = () => {
handleServerAdd,
handleServerEdit,
handleServerRemove,
handleServerToggle
handleServerToggle,
};
};
};

View File

@@ -2,6 +2,7 @@ import { useState, useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { ApiResponse } from '@/types';
import { useToast } from '@/contexts/ToastContext';
import { getApiUrl } from '../utils/api';
// Define types for the settings data
interface RoutingConfig {
@@ -80,7 +81,7 @@ export const useSettingsData = () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/settings', {
const response = await fetch(getApiUrl('/settings'), {
headers: {
'x-auth-token': token || '',
},
@@ -136,7 +137,7 @@ export const useSettingsData = () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/system-config', {
const response = await fetch(getApiUrl('/system-config'), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -183,7 +184,7 @@ export const useSettingsData = () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/system-config', {
const response = await fetch(getApiUrl('/system-config'), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -233,7 +234,7 @@ export const useSettingsData = () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/system-config', {
const response = await fetch(getApiUrl('/system-config'), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -283,7 +284,7 @@ export const useSettingsData = () => {
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch('/api/system-config', {
const response = await fetch(getApiUrl('/system-config'), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',