import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Loader2, RefreshCw, Database, CheckCircle, XCircle, AlertCircle, Clock, ToggleLeft, ToggleRight, Play, ExternalLink, Shield, Search, Swords, Bug, } from "lucide-react"; import { getDataSources, updateDataSource, syncDataSource, syncAllDataSources, type DataSource, type SyncAllResult, } from "../api/data-sources"; /** Map source type to visual props. */ function typeProps(type: string) { switch (type) { case "attack_procedure": return { label: "Attack Procedure", color: "text-red-400 bg-red-900/50 border-red-500/30", icon: Swords }; case "detection_rule": return { label: "Detection Rule", color: "text-blue-400 bg-blue-900/50 border-blue-500/30", icon: Shield }; case "threat_intel": return { label: "Threat Intel", color: "text-purple-400 bg-purple-900/50 border-purple-500/30", icon: Search }; case "defensive_technique": return { label: "Defensive", color: "text-green-400 bg-green-900/50 border-green-500/30", icon: Shield }; default: return { label: type, color: "text-gray-400 bg-gray-800/50 border-gray-600/30", icon: Bug }; } } function statusBadge(status: string | null) { if (!status) return null; switch (status) { case "success": return ( Success ); case "error": return ( Error ); case "in_progress": return ( In Progress ); default: return ( {status} ); } } export default function DataSourcesPage() { const queryClient = useQueryClient(); const [syncingId, setSyncingId] = useState(null); const [syncAllResult, setSyncAllResult] = useState(null); // ── Queries ───────────────────────────────────────────────────── const { data: sources, isLoading, error, } = useQuery({ queryKey: ["data-sources"], queryFn: getDataSources, }); // ── Toggle enable/disable ─────────────────────────────────────── const toggleMutation = useMutation({ mutationFn: ({ id, enabled }: { id: string; enabled: boolean }) => updateDataSource(id, { is_enabled: enabled }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["data-sources"] }); }, }); // ── Sync individual source ────────────────────────────────────── const syncMutation = useMutation({ mutationFn: (id: string) => syncDataSource(id), onSuccess: () => { setSyncingId(null); queryClient.invalidateQueries({ queryKey: ["data-sources"] }); }, onError: () => { setSyncingId(null); queryClient.invalidateQueries({ queryKey: ["data-sources"] }); }, }); // ── Sync all ──────────────────────────────────────────────────── const syncAllMutation = useMutation({ mutationFn: syncAllDataSources, onSuccess: (data) => { setSyncAllResult(data); queryClient.invalidateQueries({ queryKey: ["data-sources"] }); }, }); const handleSync = (id: string) => { setSyncingId(id); syncMutation.mutate(id); }; const formatDate = (dateStr: string | null) => { if (!dateStr) return "Never"; const date = new Date(dateStr); return date.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" }); }; const formatStats = (stats: Record | null) => { if (!stats) return null; return Object.entries(stats) .filter(([k]) => k !== "error") .map(([k, v]) => `${k.replace(/_/g, " ")}: ${v}`) .join(" | "); }; return (
{/* Header */}

Data Sources

Manage external data sources for test templates and detection rules

{/* Sync All Result */} {syncAllResult && (

Sync All Results

{syncAllResult.results.map((r, i) => (
{r.status === "success" ? ( ) : r.status === "error" ? ( ) : ( )} {r.source} {r.stats && ( {formatStats(r.stats as Record)} )} {r.detail && ( {r.detail} )}
))}
)} {/* Loading */} {isLoading && (
)} {/* Error */} {error && (

Failed to load data sources: {(error as Error)?.message}

)} {/* Data Sources Table */} {sources && sources.length > 0 && (
{sources.map((src: DataSource) => { const tp = typeProps(src.type); const TypeIcon = tp.icon; const isSyncing = syncingId === src.id; return ( {/* Source */} {/* Type */} {/* Sync Status */} {/* Last Sync */} {/* Stats */} {/* Toggle */} {/* Actions */} ); })}
Source Type Status Last Sync Stats Enabled Actions

{src.display_name}

{src.name} {src.url && ( )}
{tp.label} {isSyncing ? statusBadge("in_progress") : statusBadge(src.last_sync_status)}
{formatDate(src.last_sync_at)}
{src.sync_frequency && ( Frequency: {src.sync_frequency} )}
{src.last_sync_stats ? ( {formatStats(src.last_sync_stats)} ) : ( - )}
)} {/* Empty State */} {sources && sources.length === 0 && (

No Data Sources

Run the data sources seed script to register initial sources.

)}
); }