@samanhappy/mcphub 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/package.json +8 -5
  2. package/.env.example +0 -2
  3. package/.eslintrc.json +0 -25
  4. package/.github/workflows/build.yml +0 -51
  5. package/.github/workflows/release.yml +0 -19
  6. package/.prettierrc +0 -7
  7. package/Dockerfile +0 -51
  8. package/assets/amap-edit.png +0 -0
  9. package/assets/amap-result.png +0 -0
  10. package/assets/cherry-mcp.png +0 -0
  11. package/assets/cursor-mcp.png +0 -0
  12. package/assets/cursor-query.png +0 -0
  13. package/assets/cursor-tools.png +0 -0
  14. package/assets/dashboard.png +0 -0
  15. package/assets/dashboard.zh.png +0 -0
  16. package/assets/group.png +0 -0
  17. package/assets/group.zh.png +0 -0
  18. package/assets/market.zh.png +0 -0
  19. package/assets/wegroup.jpg +0 -0
  20. package/assets/wegroup.png +0 -0
  21. package/assets/wexin.png +0 -0
  22. package/bin/mcphub.js +0 -3
  23. package/doc/intro.md +0 -73
  24. package/doc/intro2.md +0 -232
  25. package/entrypoint.sh +0 -10
  26. package/frontend/favicon.ico +0 -0
  27. package/frontend/index.html +0 -13
  28. package/frontend/postcss.config.js +0 -6
  29. package/frontend/src/App.tsx +0 -44
  30. package/frontend/src/components/AddGroupForm.tsx +0 -132
  31. package/frontend/src/components/AddServerForm.tsx +0 -90
  32. package/frontend/src/components/ChangePasswordForm.tsx +0 -158
  33. package/frontend/src/components/EditGroupForm.tsx +0 -149
  34. package/frontend/src/components/EditServerForm.tsx +0 -76
  35. package/frontend/src/components/GroupCard.tsx +0 -143
  36. package/frontend/src/components/MarketServerCard.tsx +0 -153
  37. package/frontend/src/components/MarketServerDetail.tsx +0 -297
  38. package/frontend/src/components/ProtectedRoute.tsx +0 -27
  39. package/frontend/src/components/ServerCard.tsx +0 -230
  40. package/frontend/src/components/ServerForm.tsx +0 -276
  41. package/frontend/src/components/icons/LucideIcons.tsx +0 -14
  42. package/frontend/src/components/layout/Content.tsx +0 -17
  43. package/frontend/src/components/layout/Header.tsx +0 -61
  44. package/frontend/src/components/layout/Sidebar.tsx +0 -98
  45. package/frontend/src/components/ui/Badge.tsx +0 -33
  46. package/frontend/src/components/ui/Button.tsx +0 -0
  47. package/frontend/src/components/ui/DeleteDialog.tsx +0 -48
  48. package/frontend/src/components/ui/Pagination.tsx +0 -128
  49. package/frontend/src/components/ui/Toast.tsx +0 -96
  50. package/frontend/src/components/ui/ToggleGroup.tsx +0 -134
  51. package/frontend/src/components/ui/ToolCard.tsx +0 -38
  52. package/frontend/src/contexts/AuthContext.tsx +0 -159
  53. package/frontend/src/contexts/ToastContext.tsx +0 -60
  54. package/frontend/src/hooks/useGroupData.ts +0 -232
  55. package/frontend/src/hooks/useMarketData.ts +0 -410
  56. package/frontend/src/hooks/useServerData.ts +0 -306
  57. package/frontend/src/hooks/useSettingsData.ts +0 -131
  58. package/frontend/src/i18n.ts +0 -42
  59. package/frontend/src/index.css +0 -20
  60. package/frontend/src/layouts/MainLayout.tsx +0 -33
  61. package/frontend/src/locales/en.json +0 -214
  62. package/frontend/src/locales/zh.json +0 -214
  63. package/frontend/src/main.tsx +0 -12
  64. package/frontend/src/pages/Dashboard.tsx +0 -206
  65. package/frontend/src/pages/GroupsPage.tsx +0 -116
  66. package/frontend/src/pages/LoginPage.tsx +0 -104
  67. package/frontend/src/pages/MarketPage.tsx +0 -356
  68. package/frontend/src/pages/ServersPage.tsx +0 -144
  69. package/frontend/src/pages/SettingsPage.tsx +0 -149
  70. package/frontend/src/services/authService.ts +0 -141
  71. package/frontend/src/types/index.ts +0 -160
  72. package/frontend/src/utils/cn.ts +0 -10
  73. package/frontend/tsconfig.json +0 -31
  74. package/frontend/tsconfig.node.json +0 -10
  75. package/frontend/vite.config.ts +0 -26
  76. package/googled76ca578b6543fbc.html +0 -1
  77. package/jest.config.js +0 -10
  78. package/mcp_settings.json +0 -45
  79. package/servers.json +0 -74722
  80. package/src/config/index.ts +0 -46
  81. package/src/controllers/authController.ts +0 -179
  82. package/src/controllers/groupController.ts +0 -341
  83. package/src/controllers/marketController.ts +0 -154
  84. package/src/controllers/serverController.ts +0 -303
  85. package/src/index.ts +0 -17
  86. package/src/middlewares/auth.ts +0 -28
  87. package/src/middlewares/index.ts +0 -43
  88. package/src/models/User.ts +0 -103
  89. package/src/routes/index.ts +0 -96
  90. package/src/server.ts +0 -72
  91. package/src/services/groupService.ts +0 -232
  92. package/src/services/marketService.ts +0 -116
  93. package/src/services/mcpService.ts +0 -385
  94. package/src/services/sseService.ts +0 -119
  95. package/src/types/index.ts +0 -129
  96. package/src/utils/migration.ts +0 -52
  97. package/tsconfig.json +0 -17
@@ -1,410 +0,0 @@
1
- import { useState, useEffect, useCallback } from 'react';
2
- import { useTranslation } from 'react-i18next';
3
- import { MarketServer, ApiResponse } from '@/types';
4
-
5
- export const useMarketData = () => {
6
- const { t } = useTranslation();
7
- const [servers, setServers] = useState<MarketServer[]>([]);
8
- const [allServers, setAllServers] = useState<MarketServer[]>([]);
9
- const [categories, setCategories] = useState<string[]>([]);
10
- const [tags, setTags] = useState<string[]>([]);
11
- const [selectedCategory, setSelectedCategory] = useState<string>('');
12
- const [selectedTag, setSelectedTag] = useState<string>('');
13
- const [searchQuery, setSearchQuery] = useState<string>('');
14
- const [loading, setLoading] = useState(true);
15
- const [error, setError] = useState<string | null>(null);
16
- const [currentServer, setCurrentServer] = useState<MarketServer | null>(null);
17
- const [installedServers, setInstalledServers] = useState<string[]>([]);
18
-
19
- // Pagination states
20
- const [currentPage, setCurrentPage] = useState(1);
21
- const [serversPerPage, setServersPerPage] = useState(9);
22
- const [totalPages, setTotalPages] = useState(1);
23
-
24
- // Fetch all market servers
25
- const fetchMarketServers = useCallback(async () => {
26
- try {
27
- setLoading(true);
28
- const token = localStorage.getItem('mcphub_token');
29
- const response = await fetch('/api/market/servers', {
30
- headers: {
31
- 'x-auth-token': token || ''
32
- }
33
- });
34
-
35
- if (!response.ok) {
36
- throw new Error(`Status: ${response.status}`);
37
- }
38
-
39
- const data: ApiResponse<MarketServer[]> = await response.json();
40
-
41
- if (data && data.success && Array.isArray(data.data)) {
42
- setAllServers(data.data);
43
- // Apply pagination to the fetched data
44
- applyPagination(data.data, currentPage);
45
- } else {
46
- console.error('Invalid market servers data format:', data);
47
- setError(t('market.fetchError'));
48
- }
49
- } catch (err) {
50
- console.error('Error fetching market servers:', err);
51
- setError(err instanceof Error ? err.message : String(err));
52
- } finally {
53
- setLoading(false);
54
- }
55
- }, [t, currentPage]);
56
-
57
- // Apply pagination to data
58
- const applyPagination = useCallback((data: MarketServer[], page: number, itemsPerPage = serversPerPage) => {
59
- const totalItems = data.length;
60
- const calculatedTotalPages = Math.ceil(totalItems / itemsPerPage);
61
- setTotalPages(calculatedTotalPages);
62
-
63
- // Ensure current page is valid
64
- const validPage = Math.max(1, Math.min(page, calculatedTotalPages));
65
- if (validPage !== page) {
66
- setCurrentPage(validPage);
67
- }
68
-
69
- const startIndex = (validPage - 1) * itemsPerPage;
70
- const paginatedServers = data.slice(startIndex, startIndex + itemsPerPage);
71
- setServers(paginatedServers);
72
- }, [serversPerPage]);
73
-
74
- // Change page
75
- const changePage = useCallback((page: number) => {
76
- setCurrentPage(page);
77
- applyPagination(allServers, page, serversPerPage);
78
- }, [allServers, applyPagination, serversPerPage]);
79
-
80
- // Fetch all categories
81
- const fetchCategories = useCallback(async () => {
82
- try {
83
- const token = localStorage.getItem('mcphub_token');
84
- const response = await fetch('/api/market/categories', {
85
- headers: {
86
- 'x-auth-token': token || ''
87
- }
88
- });
89
-
90
- if (!response.ok) {
91
- throw new Error(`Status: ${response.status}`);
92
- }
93
-
94
- const data: ApiResponse<string[]> = await response.json();
95
-
96
- if (data && data.success && Array.isArray(data.data)) {
97
- setCategories(data.data);
98
- } else {
99
- console.error('Invalid categories data format:', data);
100
- }
101
- } catch (err) {
102
- console.error('Error fetching categories:', err);
103
- }
104
- }, []);
105
-
106
- // Fetch all tags
107
- const fetchTags = useCallback(async () => {
108
- try {
109
- const token = localStorage.getItem('mcphub_token');
110
- const response = await fetch('/api/market/tags', {
111
- headers: {
112
- 'x-auth-token': token || ''
113
- }
114
- });
115
-
116
- if (!response.ok) {
117
- throw new Error(`Status: ${response.status}`);
118
- }
119
-
120
- const data: ApiResponse<string[]> = await response.json();
121
-
122
- if (data && data.success && Array.isArray(data.data)) {
123
- setTags(data.data);
124
- } else {
125
- console.error('Invalid tags data format:', data);
126
- }
127
- } catch (err) {
128
- console.error('Error fetching tags:', err);
129
- }
130
- }, []);
131
-
132
- // Fetch server by name
133
- const fetchServerByName = useCallback(async (name: string) => {
134
- try {
135
- setLoading(true);
136
- const token = localStorage.getItem('mcphub_token');
137
- const response = await fetch(`/api/market/servers/${name}`, {
138
- headers: {
139
- 'x-auth-token': token || ''
140
- }
141
- });
142
-
143
- if (!response.ok) {
144
- throw new Error(`Status: ${response.status}`);
145
- }
146
-
147
- const data: ApiResponse<MarketServer> = await response.json();
148
-
149
- if (data && data.success && data.data) {
150
- setCurrentServer(data.data);
151
- return data.data;
152
- } else {
153
- console.error('Invalid server data format:', data);
154
- setError(t('market.serverNotFound'));
155
- return null;
156
- }
157
- } catch (err) {
158
- console.error(`Error fetching server ${name}:`, err);
159
- setError(err instanceof Error ? err.message : String(err));
160
- return null;
161
- } finally {
162
- setLoading(false);
163
- }
164
- }, [t]);
165
-
166
- // Search servers by query
167
- const searchServers = useCallback(async (query: string) => {
168
- try {
169
- setLoading(true);
170
- setSearchQuery(query);
171
-
172
- if (!query.trim()) {
173
- // Fetch fresh data from server instead of just applying pagination
174
- fetchMarketServers();
175
- return;
176
- }
177
-
178
- const token = localStorage.getItem('mcphub_token');
179
- const response = await fetch(`/api/market/servers/search?query=${encodeURIComponent(query)}`, {
180
- headers: {
181
- 'x-auth-token': token || ''
182
- }
183
- });
184
-
185
- if (!response.ok) {
186
- throw new Error(`Status: ${response.status}`);
187
- }
188
-
189
- const data: ApiResponse<MarketServer[]> = await response.json();
190
-
191
- if (data && data.success && Array.isArray(data.data)) {
192
- setAllServers(data.data);
193
- setCurrentPage(1);
194
- applyPagination(data.data, 1);
195
- } else {
196
- console.error('Invalid search results format:', data);
197
- setError(t('market.searchError'));
198
- }
199
- } catch (err) {
200
- console.error('Error searching servers:', err);
201
- setError(err instanceof Error ? err.message : String(err));
202
- } finally {
203
- setLoading(false);
204
- }
205
- }, [t, allServers, applyPagination, fetchMarketServers]);
206
-
207
- // Filter servers by category
208
- const filterByCategory = useCallback(async (category: string) => {
209
- try {
210
- setLoading(true);
211
- setSelectedCategory(category);
212
- setSelectedTag(''); // Reset tag filter when filtering by category
213
-
214
- if (!category) {
215
- fetchMarketServers();
216
- return;
217
- }
218
-
219
- const token = localStorage.getItem('mcphub_token');
220
- const response = await fetch(`/api/market/categories/${encodeURIComponent(category)}`, {
221
- headers: {
222
- 'x-auth-token': token || ''
223
- }
224
- });
225
-
226
- if (!response.ok) {
227
- throw new Error(`Status: ${response.status}`);
228
- }
229
-
230
- const data: ApiResponse<MarketServer[]> = await response.json();
231
-
232
- if (data && data.success && Array.isArray(data.data)) {
233
- setAllServers(data.data);
234
- setCurrentPage(1);
235
- applyPagination(data.data, 1);
236
- } else {
237
- console.error('Invalid category filter results format:', data);
238
- setError(t('market.filterError'));
239
- }
240
- } catch (err) {
241
- console.error('Error filtering servers by category:', err);
242
- setError(err instanceof Error ? err.message : String(err));
243
- } finally {
244
- setLoading(false);
245
- }
246
- }, [t, fetchMarketServers, applyPagination]);
247
-
248
- // Filter servers by tag
249
- const filterByTag = useCallback(async (tag: string) => {
250
- try {
251
- setLoading(true);
252
- setSelectedTag(tag);
253
- setSelectedCategory(''); // Reset category filter when filtering by tag
254
-
255
- if (!tag) {
256
- fetchMarketServers();
257
- return;
258
- }
259
-
260
- const token = localStorage.getItem('mcphub_token');
261
- const response = await fetch(`/api/market/tags/${encodeURIComponent(tag)}`, {
262
- headers: {
263
- 'x-auth-token': token || ''
264
- }
265
- });
266
-
267
- if (!response.ok) {
268
- throw new Error(`Status: ${response.status}`);
269
- }
270
-
271
- const data: ApiResponse<MarketServer[]> = await response.json();
272
-
273
- if (data && data.success && Array.isArray(data.data)) {
274
- setAllServers(data.data);
275
- setCurrentPage(1);
276
- applyPagination(data.data, 1);
277
- } else {
278
- console.error('Invalid tag filter results format:', data);
279
- setError(t('market.tagFilterError'));
280
- }
281
- } catch (err) {
282
- console.error('Error filtering servers by tag:', err);
283
- setError(err instanceof Error ? err.message : String(err));
284
- } finally {
285
- setLoading(false);
286
- }
287
- }, [t, fetchMarketServers, applyPagination]);
288
-
289
- // Fetch installed servers
290
- const fetchInstalledServers = useCallback(async () => {
291
- try {
292
- const token = localStorage.getItem('mcphub_token');
293
- const response = await fetch('/api/servers', {
294
- headers: {
295
- 'x-auth-token': token || ''
296
- }
297
- });
298
-
299
- if (!response.ok) {
300
- throw new Error(`Status: ${response.status}`);
301
- }
302
-
303
- const data = await response.json();
304
-
305
- if (data && data.success && Array.isArray(data.data)) {
306
- // Extract server names
307
- const installedServerNames = data.data.map((server: any) => server.name);
308
- setInstalledServers(installedServerNames);
309
- }
310
- } catch (err) {
311
- console.error('Error fetching installed servers:', err);
312
- }
313
- }, []);
314
-
315
- // Check if a server is already installed
316
- const isServerInstalled = useCallback((serverName: string) => {
317
- return installedServers.includes(serverName);
318
- }, [installedServers]);
319
-
320
- // Install server to the local environment
321
- const installServer = useCallback(async (server: MarketServer) => {
322
- try {
323
- const installType = server.installations?.npm ? 'npm' : Object.keys(server.installations || {}).length > 0 ? Object.keys(server.installations)[0] : null;
324
-
325
- if (!installType || !server.installations?.[installType]) {
326
- setError(t('market.noInstallationMethod'));
327
- return false;
328
- }
329
-
330
- const installation = server.installations[installType];
331
-
332
- // Prepare server configuration
333
- const serverConfig = {
334
- name: server.name,
335
- config: {
336
- command: installation.command,
337
- args: installation.args,
338
- env: installation.env || {}
339
- }
340
- };
341
-
342
- // Call the createServer API
343
- const token = localStorage.getItem('mcphub_token');
344
- const response = await fetch('/api/servers', {
345
- method: 'POST',
346
- headers: {
347
- 'Content-Type': 'application/json',
348
- 'x-auth-token': token || ''
349
- },
350
- body: JSON.stringify(serverConfig),
351
- });
352
-
353
- if (!response.ok) {
354
- const errorData = await response.json();
355
- throw new Error(errorData.message || `Status: ${response.status}`);
356
- }
357
-
358
- // Update installed servers list after successful installation
359
- await fetchInstalledServers();
360
- return true;
361
- } catch (err) {
362
- console.error('Error installing server:', err);
363
- setError(err instanceof Error ? err.message : String(err));
364
- return false;
365
- }
366
- }, [t, fetchInstalledServers]);
367
-
368
- // Change servers per page
369
- const changeServersPerPage = useCallback((perPage: number) => {
370
- setServersPerPage(perPage);
371
- setCurrentPage(1);
372
- applyPagination(allServers, 1, perPage);
373
- }, [allServers, applyPagination]);
374
-
375
- // Load initial data
376
- useEffect(() => {
377
- fetchMarketServers();
378
- fetchCategories();
379
- fetchTags();
380
- fetchInstalledServers();
381
- }, [fetchMarketServers, fetchCategories, fetchTags, fetchInstalledServers]);
382
-
383
- return {
384
- servers,
385
- allServers,
386
- categories,
387
- tags,
388
- selectedCategory,
389
- selectedTag,
390
- searchQuery,
391
- loading,
392
- error,
393
- setError,
394
- currentServer,
395
- fetchMarketServers,
396
- fetchServerByName,
397
- searchServers,
398
- filterByCategory,
399
- filterByTag,
400
- installServer,
401
- // Pagination properties and methods
402
- currentPage,
403
- totalPages,
404
- serversPerPage,
405
- changePage,
406
- changeServersPerPage,
407
- // Installed servers methods
408
- isServerInstalled
409
- };
410
- };
@@ -1,306 +0,0 @@
1
- import { useState, useEffect, useRef, useCallback } from 'react';
2
- import { useTranslation } from 'react-i18next';
3
- import { Server, ApiResponse } from '@/types';
4
-
5
- // Configuration options
6
- const CONFIG = {
7
- // Initialization phase configuration
8
- startup: {
9
- maxAttempts: 60, // Maximum number of attempts during initialization
10
- pollingInterval: 3000 // Polling interval during initialization (3 seconds)
11
- },
12
- // Normal operation phase configuration
13
- normal: {
14
- pollingInterval: 10000 // Polling interval during normal operation (10 seconds)
15
- }
16
- };
17
-
18
- export const useServerData = () => {
19
- const { t } = useTranslation();
20
- const [servers, setServers] = useState<Server[]>([]);
21
- const [error, setError] = useState<string | null>(null);
22
- const [refreshKey, setRefreshKey] = useState(0);
23
- const [isInitialLoading, setIsInitialLoading] = useState(true);
24
- const [fetchAttempts, setFetchAttempts] = useState(0);
25
-
26
- // Timer reference for polling
27
- const intervalRef = useRef<NodeJS.Timeout | null>(null);
28
- // Track current attempt count to avoid dependency cycles
29
- const attemptsRef = useRef<number>(0);
30
-
31
- // Clear the timer
32
- const clearTimer = () => {
33
- if (intervalRef.current) {
34
- clearInterval(intervalRef.current);
35
- intervalRef.current = null;
36
- }
37
- };
38
-
39
- // Start normal polling
40
- const startNormalPolling = useCallback(() => {
41
- // Ensure no other timers are running
42
- clearTimer();
43
-
44
- const fetchServers = async () => {
45
- try {
46
- const token = localStorage.getItem('mcphub_token');
47
- const response = await fetch('/api/servers', {
48
- headers: {
49
- 'x-auth-token': token || ''
50
- }
51
- });
52
- const data = await response.json();
53
-
54
- if (data && data.success && Array.isArray(data.data)) {
55
- setServers(data.data);
56
- } else if (data && Array.isArray(data)) {
57
- setServers(data);
58
- } else {
59
- console.error('Invalid server data format:', data);
60
- setServers([]);
61
- }
62
-
63
- // Reset error state
64
- setError(null);
65
- } catch (err) {
66
- console.error('Error fetching servers during normal polling:', err);
67
-
68
- // Use friendly error message
69
- if (!navigator.onLine) {
70
- setError(t('errors.network'));
71
- } else if (err instanceof TypeError && (
72
- err.message.includes('NetworkError') ||
73
- err.message.includes('Failed to fetch')
74
- )) {
75
- setError(t('errors.serverConnection'));
76
- } else {
77
- setError(t('errors.serverFetch'));
78
- }
79
- }
80
- };
81
-
82
- // Execute immediately
83
- fetchServers();
84
-
85
- // Set up regular polling
86
- intervalRef.current = setInterval(fetchServers, CONFIG.normal.pollingInterval);
87
- }, [t]);
88
-
89
- useEffect(() => {
90
- // Reset attempt count
91
- if (refreshKey > 0) {
92
- attemptsRef.current = 0;
93
- setFetchAttempts(0);
94
- }
95
-
96
- // Initialization phase request function
97
- const fetchInitialData = async () => {
98
- try {
99
- const token = localStorage.getItem('mcphub_token');
100
- const response = await fetch('/api/servers', {
101
- headers: {
102
- 'x-auth-token': token || ''
103
- }
104
- });
105
- const data = await response.json();
106
-
107
- // Handle API response wrapper object, extract data field
108
- if (data && data.success && Array.isArray(data.data)) {
109
- setServers(data.data);
110
- setIsInitialLoading(false);
111
- // Initialization successful, start normal polling
112
- startNormalPolling();
113
- return true;
114
- } else if (data && Array.isArray(data)) {
115
- // Compatibility handling, if API directly returns array
116
- setServers(data);
117
- setIsInitialLoading(false);
118
- // Initialization successful, start normal polling
119
- startNormalPolling();
120
- return true;
121
- } else {
122
- // If data format is not as expected, set to empty array
123
- console.error('Invalid server data format:', data);
124
- setServers([]);
125
- setIsInitialLoading(false);
126
- // Initialization successful but data is empty, start normal polling
127
- startNormalPolling();
128
- return true;
129
- }
130
- } catch (err) {
131
- // Increment attempt count, use ref to avoid triggering effect rerun
132
- attemptsRef.current += 1;
133
- console.error(`Initial loading attempt ${attemptsRef.current} failed:`, err);
134
-
135
- // Update state for display
136
- setFetchAttempts(attemptsRef.current);
137
-
138
- // Set appropriate error message
139
- if (!navigator.onLine) {
140
- setError(t('errors.network'));
141
- } else {
142
- setError(t('errors.initialStartup'));
143
- }
144
-
145
- // If maximum attempt count is exceeded, give up initialization and switch to normal polling
146
- if (attemptsRef.current >= CONFIG.startup.maxAttempts) {
147
- console.log('Maximum startup attempts reached, switching to normal polling');
148
- setIsInitialLoading(false);
149
- // Clear initialization polling
150
- clearTimer();
151
- // Switch to normal polling mode
152
- startNormalPolling();
153
- }
154
-
155
- return false;
156
- }
157
- };
158
-
159
- // On component mount, set appropriate polling based on current state
160
- if (isInitialLoading) {
161
- // Ensure no other timers are running
162
- clearTimer();
163
-
164
- // Execute initial request immediately
165
- fetchInitialData();
166
-
167
- // Set polling interval for initialization phase
168
- intervalRef.current = setInterval(fetchInitialData, CONFIG.startup.pollingInterval);
169
- console.log(`Started initial polling with interval: ${CONFIG.startup.pollingInterval}ms`);
170
- } else {
171
- // Initialization completed, start normal polling
172
- startNormalPolling();
173
- }
174
-
175
- // Cleanup function
176
- return () => {
177
- clearTimer();
178
- };
179
- }, [refreshKey, t, isInitialLoading, startNormalPolling]);
180
-
181
- // Manually trigger refresh
182
- const triggerRefresh = () => {
183
- // Clear current timer
184
- clearTimer();
185
-
186
- // If in initialization phase, reset initialization state
187
- if (isInitialLoading) {
188
- setIsInitialLoading(true);
189
- attemptsRef.current = 0;
190
- setFetchAttempts(0);
191
- }
192
-
193
- // Change in refreshKey will trigger useEffect to run again
194
- setRefreshKey(prevKey => prevKey + 1);
195
- };
196
-
197
- // Server related operations
198
- const handleServerAdd = () => {
199
- setRefreshKey(prevKey => prevKey + 1);
200
- };
201
-
202
- const handleServerEdit = async (server: Server) => {
203
- try {
204
- // Fetch settings to get the full server config before editing
205
- const token = localStorage.getItem('mcphub_token');
206
- const response = await fetch(`/api/settings`, {
207
- headers: {
208
- 'x-auth-token': token || ''
209
- }
210
- });
211
-
212
- const settingsData: ApiResponse<{ mcpServers: Record<string, any> }> = await response.json();
213
-
214
- if (
215
- settingsData &&
216
- settingsData.success &&
217
- settingsData.data &&
218
- settingsData.data.mcpServers &&
219
- settingsData.data.mcpServers[server.name]
220
- ) {
221
- const serverConfig = settingsData.data.mcpServers[server.name];
222
- return {
223
- name: server.name,
224
- status: server.status,
225
- tools: server.tools || [],
226
- config: serverConfig,
227
- };
228
- } else {
229
- console.error('Failed to get server config from settings:', settingsData);
230
- setError(t('server.invalidConfig', { serverName: server.name }));
231
- return null;
232
- }
233
- } catch (err) {
234
- console.error('Error fetching server settings:', err);
235
- setError(err instanceof Error ? err.message : String(err));
236
- return null;
237
- }
238
- };
239
-
240
- const handleServerRemove = async (serverName: string) => {
241
- try {
242
- const token = localStorage.getItem('mcphub_token');
243
- const response = await fetch(`/api/servers/${serverName}`, {
244
- method: 'DELETE',
245
- headers: {
246
- 'x-auth-token': token || ''
247
- }
248
- });
249
- const result = await response.json();
250
-
251
- if (!response.ok) {
252
- setError(result.message || t('server.deleteError', { serverName }));
253
- return false;
254
- }
255
-
256
- setRefreshKey(prevKey => prevKey + 1);
257
- return true;
258
- } catch (err) {
259
- setError(t('errors.general') + ': ' + (err instanceof Error ? err.message : String(err)));
260
- return false;
261
- }
262
- };
263
-
264
- const handleServerToggle = async (server: Server, enabled: boolean) => {
265
- try {
266
- const token = localStorage.getItem('mcphub_token');
267
- const response = await fetch(`/api/servers/${server.name}/toggle`, {
268
- method: 'POST',
269
- headers: {
270
- 'Content-Type': 'application/json',
271
- 'x-auth-token': token || ''
272
- },
273
- body: JSON.stringify({ enabled }),
274
- });
275
-
276
- const result = await response.json();
277
-
278
- if (!response.ok) {
279
- console.error('Failed to toggle server:', result);
280
- setError(t('server.toggleError', { serverName: server.name }));
281
- return false;
282
- }
283
-
284
- // Update the UI immediately to reflect the change
285
- setRefreshKey(prevKey => prevKey + 1);
286
- return true;
287
- } catch (err) {
288
- console.error('Error toggling server:', err);
289
- setError(err instanceof Error ? err.message : String(err));
290
- return false;
291
- }
292
- };
293
-
294
- return {
295
- servers,
296
- error,
297
- setError,
298
- isLoading: isInitialLoading,
299
- fetchAttempts,
300
- triggerRefresh,
301
- handleServerAdd,
302
- handleServerEdit,
303
- handleServerRemove,
304
- handleServerToggle
305
- };
306
- };