"use client" import { useState } from "react" import { Database, Plus, Search, Play, Pause, RefreshCw, Settings, Trash2, CheckCircle, XCircle, Clock, Server, FileText, Globe, AlertTriangle, Link2, } from "lucide-react" import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Badge } from "@/components/ui/badge" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Textarea } from "@/components/ui/textarea" interface DataSource { id: string name: string type: "mysql" | "postgresql" | "restapi" | "webhook" | "sftp" | "kafka" status: "running" | "stopped" | "error" | "syncing" syncMode: "full" | "incremental" | "realtime" frequency: string lastSync: string records: number errorCount: number config: Record } const MOCK_SOURCES: DataSource[] = [ { id: "1", name: "存客宝-用户主库", type: "mysql", status: "running", syncMode: "incremental", frequency: "每5分钟", lastSync: "2025-12-12 14:30:00", records: 128956342, errorCount: 0, config: { host: "db.cunkebao.com", port: "3306", database: "user_main" }, }, { id: "2", name: "触客宝-行为数据", type: "kafka", status: "running", syncMode: "realtime", frequency: "实时", lastSync: "2025-12-12 14:35:12", records: 89234567, errorCount: 2, config: { brokers: "kafka.chukebao.com:9092", topic: "user_behavior" }, }, { id: "3", name: "数智员工-账号API", type: "restapi", status: "running", syncMode: "incremental", frequency: "每小时", lastSync: "2025-12-12 14:00:00", records: 45678901, errorCount: 0, config: { endpoint: "https://api.shuzhi.com/v1/accounts", method: "GET", auth: "Bearer Token" }, }, { id: "4", name: "征信数据-外部接口", type: "restapi", status: "stopped", syncMode: "incremental", frequency: "每天", lastSync: "2025-12-11 00:00:00", records: 12890456, errorCount: 0, config: { endpoint: "https://credit.external.com/api", method: "POST", auth: "API Key" }, }, { id: "5", name: "交易流水-SFTP", type: "sftp", status: "running", syncMode: "incremental", frequency: "每30分钟", lastSync: "2025-12-12 14:30:00", records: 256789012, errorCount: 1, config: { host: "sftp.bank.com", path: "/data/transactions" }, }, { id: "6", name: "聚宝盆-指标数据", type: "postgresql", status: "error", syncMode: "full", frequency: "每天", lastSync: "2025-12-10 00:00:00", records: 8901234, errorCount: 15, config: { host: "pg.jubao.com", port: "5432", database: "metrics" }, }, { id: "7", name: "微信消息-Webhook", type: "webhook", status: "running", syncMode: "realtime", frequency: "实时推送", lastSync: "2025-12-12 14:36:00", records: 34567890, errorCount: 0, config: { webhookUrl: "/api/webhook/wechat", secret: "***" }, }, ] const TYPE_ICONS = { mysql: Database, postgresql: Database, restapi: Globe, webhook: Link2, sftp: FileText, kafka: Server, } const TYPE_COLORS = { mysql: "bg-blue-100 text-blue-700", postgresql: "bg-indigo-100 text-indigo-700", restapi: "bg-green-100 text-green-700", webhook: "bg-teal-100 text-teal-700", sftp: "bg-yellow-100 text-yellow-700", kafka: "bg-purple-100 text-purple-700", } const TYPE_LABELS = { mysql: "MySQL", postgresql: "PostgreSQL", restapi: "REST API", webhook: "Webhook", sftp: "SFTP", kafka: "Kafka", } const STATUS_CONFIG = { running: { color: "bg-green-100 text-green-700", icon: CheckCircle, label: "运行中" }, stopped: { color: "bg-gray-100 text-gray-700", icon: Pause, label: "已停止" }, error: { color: "bg-red-100 text-red-700", icon: XCircle, label: "错误" }, syncing: { color: "bg-blue-100 text-blue-700", icon: RefreshCw, label: "同步中" }, } const syncLogs = [ { id: 1, time: "2025-12-12 14:30:00", level: "info", message: "开始增量同步...", records: null }, { id: 2, time: "2025-12-12 14:30:05", level: "info", message: "获取增量数据 2,350 条", records: 2350 }, { id: 3, time: "2025-12-12 14:30:12", level: "success", message: "同步完成,写入 2,350 条", records: 2350 }, { id: 4, time: "2025-12-12 14:25:00", level: "info", message: "开始增量同步...", records: null }, { id: 5, time: "2025-12-12 14:25:08", level: "success", message: "同步完成,写入 1,890 条", records: 1890 }, ] export default function DataSourcesPage() { const [sources, setSources] = useState(MOCK_SOURCES) const [searchQuery, setSearchQuery] = useState("") const [filterType, setFilterType] = useState("all") const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [settingsDialogOpen, setSettingsDialogOpen] = useState(false) const [logsDialogOpen, setLogsDialogOpen] = useState(false) const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [selectedSource, setSelectedSource] = useState(null) const [newSource, setNewSource] = useState({ name: "", type: "mysql", host: "", port: "", database: "", // API配置 endpoint: "", method: "GET", authType: "none", authToken: "", headers: "", // Webhook配置 webhookPath: "", webhookSecret: "", }) const [addStep, setAddStep] = useState(1) const filteredSources = sources.filter((source) => { const matchesSearch = source.name.toLowerCase().includes(searchQuery.toLowerCase()) const matchesType = filterType === "all" || source.type === filterType return matchesSearch && matchesType }) const stats = { total: sources.length, running: sources.filter((s) => s.status === "running").length, error: sources.filter((s) => s.status === "error").length, totalRecords: sources.reduce((sum, s) => sum + s.records, 0), apiCount: sources.filter((s) => s.type === "restapi" || s.type === "webhook").length, } const formatNumber = (num: number): string => { if (num >= 1000000000) return `${(num / 1000000000).toFixed(2)}B` if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M` if (num >= 1000) return `${(num / 1000).toFixed(1)}K` return num.toString() } const handleToggleStatus = (id: string) => { setSources((prev) => prev.map((s) => { if (s.id === id) { return { ...s, status: s.status === "running" ? "stopped" : "running" } } return s }), ) } const handleAddSource = () => { const newId = (sources.length + 1).toString() const newSourceData: DataSource = { id: newId, name: newSource.name, type: newSource.type as DataSource["type"], status: "stopped", syncMode: newSource.type === "webhook" ? "realtime" : "incremental", frequency: newSource.type === "webhook" ? "实时推送" : "每小时", lastSync: "-", records: 0, errorCount: 0, config: newSource.type === "restapi" ? { endpoint: newSource.endpoint, method: newSource.method, auth: newSource.authType } : newSource.type === "webhook" ? { webhookUrl: newSource.webhookPath, secret: "***" } : { host: newSource.host, port: newSource.port, database: newSource.database }, } setSources((prev) => [...prev, newSourceData]) setIsAddDialogOpen(false) setAddStep(1) setNewSource({ name: "", type: "mysql", host: "", port: "", database: "", endpoint: "", method: "GET", authType: "none", authToken: "", headers: "", webhookPath: "", webhookSecret: "", }) } const handleDeleteSource = () => { if (selectedSource) { setSources((prev) => prev.filter((s) => s.id !== selectedSource.id)) setDeleteDialogOpen(false) setSelectedSource(null) } } const renderConfigForm = () => { switch (newSource.type) { case "restapi": return (
setNewSource({ ...newSource, endpoint: e.target.value })} />
{newSource.authType !== "none" && (
setNewSource({ ...newSource, authToken: e.target.value })} />
)}