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}
/>
)
})}
)
}