import { useDataSources, useUpdateDataSources, useTestDataSource, useRegisteredProviders } from "@/hooks/useConfig" import { DataSourceConfig, DataSourceProvider, DataSourceProviders } from "@/types/config" import { useToast } from "@/hooks/use-toast" import { DynamicConfigForm } from "@/components/config/DynamicConfigForm" export function DataSourceTab() { const { data: dataSources, isLoading: isConfigLoading } = useDataSources(); const { data: providersMetadata, isLoading: isMetadataLoading } = useRegisteredProviders(); const updateDataSources = useUpdateDataSources(); const testDataSource = useTestDataSource(); const { toast } = useToast(); if (isConfigLoading || isMetadataLoading) { return
Loading data sources...
; } if (!providersMetadata || providersMetadata.length === 0) { return (
No data providers registered. Please ensure provider services are running.
); } const handleSave = (providerId: string, config: DataSourceConfig) => { // We need to reconstruct the full DataSourcesConfig map // Note: dataSources is a HashMap const newDataSources = { ...dataSources, [providerId]: config }; updateDataSources.mutate(newDataSources, { onSuccess: () => toast({ title: "Success", description: `${providerId} configuration saved` }), onError: (err) => toast({ title: "Error", description: "Failed to save: " + err, type: "error" }) }); }; const handleTest = (providerId: string, config: DataSourceConfig) => { // Construct payload for generic test endpoint const payload = { type: providerId, data: config // Send the whole config object as data }; // We cast to any because TestConfigRequest type definition in frontend might be loose or generic testDataSource.mutate(payload as any, { onSuccess: (data: any) => { console.log("Test Connection Success Payload:", data); // Check both explicit false and explicit "false" string, or any other falsy indicator if (data.success === false || data.success === "false") { console.warn("Test reported success but payload indicates failure:", data); toast({ title: "Test Failed", description: data.message || "Connection test failed", type: "error" }); } else { console.log("Test confirmed successful."); toast({ title: "Success", description: data.message || "Connection test successful" }); } }, onError: (err: any) => { console.error("Test Connection Error:", err); let errorMessage = "Connection test failed"; if (err.response?.data) { const data = err.response.data; console.log("Error Response Data:", data); // Try to parse 'details' if it exists and is a string (common-contracts error format) if (typeof data.details === 'string') { try { const parsed = JSON.parse(data.details); if (parsed.message) errorMessage = parsed.message; else errorMessage = data.details; } catch (e) { errorMessage = data.details; } } else if (data.message) { errorMessage = data.message; } else if (data.error) { errorMessage = data.error; } } toast({ title: "Test Failed", description: errorMessage, type: "error" }); } }); }; return (
{providersMetadata.map(meta => { // Find existing config or create default const configEntry = dataSources ? (dataSources as Record)[meta.id] : undefined; // We know that meta.id must be a valid DataSourceProvider because the backend // only registers providers that are part of the enum system. // However, meta.id comes as lowercase (e.g., "tushare") while the Enum expects PascalCase (e.g., "Tushare"). // To maintain strict type safety and follow the Single Source of Truth, // we need to cast or map it correctly. // Since we cannot change the backend serialization easily without breaking other things, // and we must respect the Zod schema, we try to match it case-insensitively to the Enum. let providerEnum = Object.values(DataSourceProviders).find( (p) => p.toLowerCase() === meta.id.toLowerCase() ); if (!providerEnum) { console.warn(`Provider ID '${meta.id}' from metadata does not match any known DataSourceProvider enum.`); // Fallback or skip? If we skip, the user can't configure it. // If we cast forcefully, Zod might reject it on save. // Let's attempt to use it as is but cast to satisfy TS, acknowledging the risk if it doesn't match. providerEnum = meta.id as DataSourceProvider; } const config = (configEntry || { provider: providerEnum, enabled: false, // We init other fields as empty, they will be filled by DynamicConfigForm }) as DataSourceConfig; return ( handleSave(meta.id, c)} onTest={(c) => handleTest(meta.id, c)} isSaving={updateDataSources.isPending} isTesting={testDataSource.isPending} /> ) })}
) }