mirror of
https://github.com/samanhappy/mcphub.git
synced 2026-01-09 08:08:16 -05:00
Enhance server data handling with allServers (#553)
This commit is contained in:
@@ -1,67 +1,67 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGroupData } from '@/hooks/useGroupData'
|
||||
import { useServerData } from '@/hooks/useServerData'
|
||||
import { GroupFormData, Server, IGroupServerConfig } from '@/types'
|
||||
import { ServerToolConfig } from './ServerToolConfig'
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGroupData } from '@/hooks/useGroupData';
|
||||
import { useServerData } from '@/hooks/useServerData';
|
||||
import { GroupFormData, Server, IGroupServerConfig } from '@/types';
|
||||
import { ServerToolConfig } from './ServerToolConfig';
|
||||
|
||||
interface AddGroupFormProps {
|
||||
onAdd: () => void
|
||||
onCancel: () => void
|
||||
onAdd: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const AddGroupForm = ({ onAdd, onCancel }: AddGroupFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { createGroup } = useGroupData()
|
||||
const { servers } = useServerData()
|
||||
const [availableServers, setAvailableServers] = useState<Server[]>([])
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const { t } = useTranslation();
|
||||
const { createGroup } = useGroupData();
|
||||
const { allServers } = useServerData();
|
||||
const [availableServers, setAvailableServers] = useState<Server[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState<GroupFormData>({
|
||||
name: '',
|
||||
description: '',
|
||||
servers: [] as IGroupServerConfig[]
|
||||
})
|
||||
servers: [] as IGroupServerConfig[],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Filter available servers (enabled only)
|
||||
setAvailableServers(servers.filter(server => server.enabled !== false))
|
||||
}, [servers])
|
||||
setAvailableServers(allServers.filter((server) => server.enabled !== false));
|
||||
}, [allServers]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target
|
||||
setFormData(prev => ({
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}))
|
||||
}
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
setError(null)
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
if (!formData.name.trim()) {
|
||||
setError(t('groups.nameRequired'))
|
||||
setIsSubmitting(false)
|
||||
return
|
||||
setError(t('groups.nameRequired'));
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await createGroup(formData.name, formData.description, formData.servers)
|
||||
const result = await createGroup(formData.name, formData.description, formData.servers);
|
||||
if (!result || !result.success) {
|
||||
setError(result?.message || t('groups.createError'))
|
||||
setIsSubmitting(false)
|
||||
return
|
||||
setError(result?.message || t('groups.createError'));
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
onAdd()
|
||||
onAdd();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
setIsSubmitting(false)
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
||||
@@ -102,7 +102,7 @@ const AddGroupForm = ({ onAdd, onCancel }: AddGroupFormProps) => {
|
||||
<ServerToolConfig
|
||||
servers={availableServers}
|
||||
value={formData.servers as IGroupServerConfig[]}
|
||||
onChange={(servers) => setFormData(prev => ({ ...prev, servers }))}
|
||||
onChange={(servers) => setFormData((prev) => ({ ...prev, servers }))}
|
||||
className="border border-gray-200 rounded-lg p-4 bg-gray-50"
|
||||
/>
|
||||
</div>
|
||||
@@ -129,7 +129,7 @@ const AddGroupForm = ({ onAdd, onCancel }: AddGroupFormProps) => {
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default AddGroupForm
|
||||
export default AddGroupForm;
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Group, GroupFormData, Server, IGroupServerConfig } from '@/types'
|
||||
import { useGroupData } from '@/hooks/useGroupData'
|
||||
import { useServerData } from '@/hooks/useServerData'
|
||||
import { ServerToolConfig } from './ServerToolConfig'
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Group, GroupFormData, Server, IGroupServerConfig } from '@/types';
|
||||
import { useGroupData } from '@/hooks/useGroupData';
|
||||
import { useServerData } from '@/hooks/useServerData';
|
||||
import { ServerToolConfig } from './ServerToolConfig';
|
||||
|
||||
interface EditGroupFormProps {
|
||||
group: Group
|
||||
onEdit: () => void
|
||||
onCancel: () => void
|
||||
group: Group;
|
||||
onEdit: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const EditGroupForm = ({ group, onEdit, onCancel }: EditGroupFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { updateGroup } = useGroupData()
|
||||
const { servers } = useServerData()
|
||||
const [availableServers, setAvailableServers] = useState<Server[]>([])
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const { t } = useTranslation();
|
||||
const { updateGroup } = useGroupData();
|
||||
const { allServers } = useServerData();
|
||||
const [availableServers, setAvailableServers] = useState<Server[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState<GroupFormData>({
|
||||
name: group.name,
|
||||
description: group.description || '',
|
||||
servers: group.servers || []
|
||||
})
|
||||
servers: group.servers || [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Filter available servers (enabled only)
|
||||
setAvailableServers(servers.filter(server => server.enabled !== false))
|
||||
}, [servers])
|
||||
setAvailableServers(allServers.filter((server) => server.enabled !== false));
|
||||
}, [allServers]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target
|
||||
setFormData(prev => ({
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}))
|
||||
}
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
setError(null)
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
if (!formData.name.trim()) {
|
||||
setError(t('groups.nameRequired'))
|
||||
setIsSubmitting(false)
|
||||
return
|
||||
setError(t('groups.nameRequired'));
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await updateGroup(group.id, {
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
servers: formData.servers
|
||||
})
|
||||
servers: formData.servers,
|
||||
});
|
||||
|
||||
if (!result || !result.success) {
|
||||
setError(result?.message || t('groups.updateError'))
|
||||
setIsSubmitting(false)
|
||||
return
|
||||
setError(result?.message || t('groups.updateError'));
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
onEdit()
|
||||
onEdit();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
setIsSubmitting(false)
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
||||
@@ -108,7 +108,7 @@ const EditGroupForm = ({ group, onEdit, onCancel }: EditGroupFormProps) => {
|
||||
<ServerToolConfig
|
||||
servers={availableServers}
|
||||
value={formData.servers as IGroupServerConfig[]}
|
||||
onChange={(servers) => setFormData(prev => ({ ...prev, servers }))}
|
||||
onChange={(servers) => setFormData((prev) => ({ ...prev, servers }))}
|
||||
className="border border-gray-200 rounded-lg p-4 bg-gray-50"
|
||||
/>
|
||||
</div>
|
||||
@@ -135,7 +135,7 @@ const EditGroupForm = ({ group, onEdit, onCancel }: EditGroupFormProps) => {
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default EditGroupForm
|
||||
export default EditGroupForm;
|
||||
|
||||
@@ -30,6 +30,7 @@ interface PaginationInfo {
|
||||
// Context type definition
|
||||
interface ServerContextType {
|
||||
servers: Server[];
|
||||
allServers: Server[]; // All servers without pagination, for Dashboard, Groups, Settings
|
||||
error: string | null;
|
||||
setError: (error: string | null) => void;
|
||||
isLoading: boolean;
|
||||
@@ -56,6 +57,7 @@ export const ServerProvider: React.FC<{ children: React.ReactNode }> = ({ childr
|
||||
const { t } = useTranslation();
|
||||
const { auth } = useAuth();
|
||||
const [servers, setServers] = useState<Server[]>([]);
|
||||
const [allServers, setAllServers] = useState<Server[]>([]); // All servers without pagination
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
const [isInitialLoading, setIsInitialLoading] = useState(true);
|
||||
@@ -95,29 +97,44 @@ export const ServerProvider: React.FC<{ children: React.ReactNode }> = ({ childr
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', currentPage.toString());
|
||||
params.append('limit', serversPerPage.toString());
|
||||
const data = await apiGet(`/servers?${params.toString()}`);
|
||||
|
||||
// Fetch both paginated servers and all servers in parallel
|
||||
const [paginatedData, allData] = await Promise.all([
|
||||
apiGet(`/servers?${params.toString()}`),
|
||||
apiGet('/servers'), // Fetch all servers without pagination
|
||||
]);
|
||||
|
||||
// Update last fetch time
|
||||
lastFetchTimeRef.current = Date.now();
|
||||
|
||||
if (data && data.success && Array.isArray(data.data)) {
|
||||
setServers(data.data);
|
||||
// Handle paginated response
|
||||
if (paginatedData && paginatedData.success && Array.isArray(paginatedData.data)) {
|
||||
setServers(paginatedData.data);
|
||||
// Update pagination info if available
|
||||
if (data.pagination) {
|
||||
setPagination(data.pagination);
|
||||
if (paginatedData.pagination) {
|
||||
setPagination(paginatedData.pagination);
|
||||
} else {
|
||||
setPagination(null);
|
||||
}
|
||||
} else if (data && Array.isArray(data)) {
|
||||
} else if (paginatedData && Array.isArray(paginatedData)) {
|
||||
// Compatibility handling for non-paginated responses
|
||||
setServers(data);
|
||||
setServers(paginatedData);
|
||||
setPagination(null);
|
||||
} else {
|
||||
console.error('Invalid server data format:', data);
|
||||
console.error('Invalid server data format:', paginatedData);
|
||||
setServers([]);
|
||||
setPagination(null);
|
||||
}
|
||||
|
||||
// Handle all servers response
|
||||
if (allData && allData.success && Array.isArray(allData.data)) {
|
||||
setAllServers(allData.data);
|
||||
} else if (allData && Array.isArray(allData)) {
|
||||
setAllServers(allData);
|
||||
} else {
|
||||
setAllServers([]);
|
||||
}
|
||||
|
||||
// Reset error state
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
@@ -159,6 +176,7 @@ export const ServerProvider: React.FC<{ children: React.ReactNode }> = ({ childr
|
||||
// When user logs out, clear data and stop polling
|
||||
clearTimer();
|
||||
setServers([]);
|
||||
setAllServers([]);
|
||||
setIsInitialLoading(false);
|
||||
setError(null);
|
||||
}
|
||||
@@ -185,42 +203,49 @@ export const ServerProvider: React.FC<{ children: React.ReactNode }> = ({ childr
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', currentPage.toString());
|
||||
params.append('limit', serversPerPage.toString());
|
||||
const data = await apiGet(`/servers?${params.toString()}`);
|
||||
|
||||
// Fetch both paginated servers and all servers in parallel
|
||||
const [paginatedData, allData] = await Promise.all([
|
||||
apiGet(`/servers?${params.toString()}`),
|
||||
apiGet('/servers'), // Fetch all servers without pagination
|
||||
]);
|
||||
|
||||
// Update last fetch time
|
||||
lastFetchTimeRef.current = Date.now();
|
||||
|
||||
// Handle API response wrapper object, extract data field
|
||||
if (data && data.success && Array.isArray(data.data)) {
|
||||
setServers(data.data);
|
||||
// Handle paginated API response wrapper object, extract data field
|
||||
if (paginatedData && paginatedData.success && Array.isArray(paginatedData.data)) {
|
||||
setServers(paginatedData.data);
|
||||
// Update pagination info if available
|
||||
if (data.pagination) {
|
||||
setPagination(data.pagination);
|
||||
if (paginatedData.pagination) {
|
||||
setPagination(paginatedData.pagination);
|
||||
} else {
|
||||
setPagination(null);
|
||||
}
|
||||
setIsInitialLoading(false);
|
||||
// Initialization successful, start normal polling (skip immediate to avoid duplicate fetch)
|
||||
startNormalPolling({ immediate: false });
|
||||
return true;
|
||||
} else if (data && Array.isArray(data)) {
|
||||
} else if (paginatedData && Array.isArray(paginatedData)) {
|
||||
// Compatibility handling, if API directly returns array
|
||||
setServers(data);
|
||||
setServers(paginatedData);
|
||||
setPagination(null);
|
||||
setIsInitialLoading(false);
|
||||
// Initialization successful, start normal polling (skip immediate to avoid duplicate fetch)
|
||||
startNormalPolling({ immediate: false });
|
||||
return true;
|
||||
} else {
|
||||
// If data format is not as expected, set to empty array
|
||||
console.error('Invalid server data format:', data);
|
||||
console.error('Invalid server data format:', paginatedData);
|
||||
setServers([]);
|
||||
setPagination(null);
|
||||
setIsInitialLoading(false);
|
||||
// Initialization successful but data is empty, start normal polling (skip immediate)
|
||||
startNormalPolling({ immediate: false });
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle all servers response
|
||||
if (allData && allData.success && Array.isArray(allData.data)) {
|
||||
setAllServers(allData.data);
|
||||
} else if (allData && Array.isArray(allData)) {
|
||||
setAllServers(allData);
|
||||
} else {
|
||||
setAllServers([]);
|
||||
}
|
||||
|
||||
setIsInitialLoading(false);
|
||||
// Initialization successful, start normal polling (skip immediate to avoid duplicate fetch)
|
||||
startNormalPolling({ immediate: false });
|
||||
return true;
|
||||
} catch (err) {
|
||||
// Increment attempt count, use ref to avoid triggering effect rerun
|
||||
attemptsRef.current += 1;
|
||||
@@ -439,6 +464,7 @@ export const ServerProvider: React.FC<{ children: React.ReactNode }> = ({ childr
|
||||
|
||||
const value: ServerContextType = {
|
||||
servers,
|
||||
allServers,
|
||||
error,
|
||||
setError,
|
||||
isLoading: isInitialLoading,
|
||||
|
||||
@@ -5,15 +5,15 @@ import { Server } from '@/types';
|
||||
|
||||
const DashboardPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { servers, error, setError, isLoading } = useServerData({ refreshOnMount: true });
|
||||
const { allServers, error, setError, isLoading } = useServerData({ refreshOnMount: true });
|
||||
|
||||
// Calculate server statistics
|
||||
// Calculate server statistics using allServers (not paginated)
|
||||
const serverStats = {
|
||||
total: servers.length,
|
||||
online: servers.filter((server: Server) => server.status === 'connected').length,
|
||||
offline: servers.filter((server: Server) => server.status === 'disconnected').length,
|
||||
connecting: servers.filter((server: Server) => server.status === 'connecting').length,
|
||||
oauthRequired: servers.filter((server: Server) => server.status === 'oauth_required').length,
|
||||
total: allServers.length,
|
||||
online: allServers.filter((server: Server) => server.status === 'connected').length,
|
||||
offline: allServers.filter((server: Server) => server.status === 'disconnected').length,
|
||||
connecting: allServers.filter((server: Server) => server.status === 'connecting').length,
|
||||
oauthRequired: allServers.filter((server: Server) => server.status === 'oauth_required').length,
|
||||
};
|
||||
|
||||
// Map status to translation keys
|
||||
@@ -202,7 +202,7 @@ const DashboardPage: React.FC = () => {
|
||||
)}
|
||||
|
||||
{/* Recent activity list */}
|
||||
{servers.length > 0 && !isLoading && (
|
||||
{allServers.length > 0 && !isLoading && (
|
||||
<div className="mt-8">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">
|
||||
{t('pages.dashboard.recentServers')}
|
||||
@@ -244,7 +244,7 @@ const DashboardPage: React.FC = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{servers.slice(0, 5).map((server, index) => (
|
||||
{allServers.slice(0, 5).map((server, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
{server.name}
|
||||
|
||||
@@ -18,7 +18,7 @@ const GroupsPage: React.FC = () => {
|
||||
deleteGroup,
|
||||
triggerRefresh,
|
||||
} = useGroupData();
|
||||
const { servers } = useServerData({ refreshOnMount: true });
|
||||
const { allServers } = useServerData({ refreshOnMount: true });
|
||||
|
||||
const [editingGroup, setEditingGroup] = useState<Group | null>(null);
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
@@ -140,7 +140,7 @@ const GroupsPage: React.FC = () => {
|
||||
<GroupCard
|
||||
key={group.id}
|
||||
group={group}
|
||||
servers={servers}
|
||||
servers={allServers}
|
||||
onEdit={handleEditClick}
|
||||
onDelete={handleDeleteGroup}
|
||||
/>
|
||||
|
||||
@@ -378,7 +378,7 @@ const SettingsPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { showToast } = useToast();
|
||||
const { servers } = useServerContext();
|
||||
const { allServers: servers } = useServerContext(); // Use allServers for settings (not paginated)
|
||||
const { groups } = useGroupData();
|
||||
|
||||
const [installConfig, setInstallConfig] = useState<{
|
||||
|
||||
Reference in New Issue
Block a user