import { useCallback, useEffect, useMemo, useState } from "react"; import { App as AntApp, Alert, Button, Card, Empty, Input, Select, Space, Statistic, Table, Tabs, Tag, Typography } from "antd"; import { ProCard, ProTable } from "@ant-design/pro-components"; import { CheckCircleOutlined, DesktopOutlined, MessageOutlined, ReloadOutlined, SafetyCertificateOutlined, SearchOutlined, TeamOutlined, UnorderedListOutlined, WarningOutlined, } from "@ant-design/icons"; import { api, activeAccount, dataPath, formatTime, isClientAvailable, normalizeContacts, shortId, statusColor, statusLabel } from "../api.js"; const { Text, Title } = Typography; export function OverviewView({ nodes, tasks, events, onNavigate, onRefresh, refreshing }) { const online = nodes.filter(isClientAvailable).length; const pending = tasks.filter((task) => ["Pending", "WaitingForClient", "Accepted", "Running"].includes(task.status)).length; const failed = tasks.filter((task) => ["Failed", "ResultUnconfirmed"].includes(task.status)).length; const recent = [...events].sort((a, b) => new Date(b.received_at || 0) - new Date(a.received_at || 0)).slice(0, 5); return (
实时运行状态真实连接状态 · 按用户分配范围隔离 · 管理操作留有审计记录
} /> } /> } valueStyle={{ color: failed ? "#cf1322" : undefined }} /> } />
onNavigate("agents")}>查看全部}> {nodes.length === 0 ? : (
{nodes.slice(0, 6).map((node) =>
{node.node_id}{node.active_account_id || "尚未确认活动账号"}{statusLabel(node.status)}
)}
)}
最多展示 5 条}> {recent.length ?
{recent.map((event) =>
{event.chat_id || event.event_type || "授权事件"}{event.node_id} · {event.chat_type === "Group" ? "群聊" : "私聊"}
{formatTime(event.received_at)}
)}
: }
); } export function ContactsView({ token, client }) { const { message } = AntApp.useApp(); const clientId = client?.node_id || ""; const accountId = activeAccount(client); const [groupsOnly, setGroupsOnly] = useState(false); const [query, setQuery] = useState(""); const [contacts, setContacts] = useState([]); const [syncInfo, setSyncInfo] = useState(null); const [hasMore, setHasMore] = useState(false); const [syncing, setSyncing] = useState(false); const [state, setState] = useState({ loading: false, error: "" }); const [requestVersion, setRequestVersion] = useState(0); const load = useCallback(async (append = false) => { if (!clientId || !accountId) return; setState({ loading: true, error: "" }); try { const params = new URLSearchParams({ limit: "200", offset: String(append ? contacts.length : 0), groups_only: String(groupsOnly) }); if (query.trim()) params.set("contains", query.trim()); const content = await api(dataPath(accountId, "contacts", `?${params.toString()}`), { token }); const next = normalizeContacts(content); setContacts((current) => append ? [...current, ...next] : next); setHasMore(Boolean(content.has_more)); setSyncInfo(content.sync || null); setState({ loading: false, error: "" }); } catch (reason) { setState({ loading: false, error: reason.message }); } }, [accountId, clientId, contacts.length, groupsOnly, query, token]); useEffect(() => { setContacts([]); setHasMore(false); setSyncInfo(null); if (clientId && accountId) void load(); }, [clientId, accountId, groupsOnly, requestVersion]); const syncContacts = async () => { if (!clientId || !accountId || syncing) return; setSyncing(true); try { await api(dataPath(accountId, "refresh"), { token, method: "POST", body: { stream_key: "contacts" } }); message.success("通讯录同步任务已提交;完成后刷新缓存查看最新数据。"); } catch (reason) { message.error(reason.message); } finally { setSyncing(false); } }; const visible = contacts.filter((contact) => !query || `${contact.title} ${contact.id} ${contact.detail}`.toLowerCase().includes(query.toLowerCase())); const columns = [ { title: "联系人 / 群聊", dataIndex: "title", render: (_, contact) => {contact.title?.slice(0, 1)}{contact.title}
{contact.type === "Group" ? "群聊" : "联系人"}
}, { title: "会话 ID", dataIndex: "id", render: (id) => {shortId(id, 32)} }, { title: "备注 / 账号", dataIndex: "detail", render: (value) => value || "—" }, ]; if (!client) return ; return (
{syncInfo?.state || "尚未同步"}}>
setQuery(event.target.value)} onSearch={() => void load()} placeholder="搜索联系人或群聊" style={{ maxWidth: 360 }} /> ({ value, label: statusLabel(value) }))]} />]} />
; } export function BroadcastView({ client }) { return (
当前上下文:{client?.node_id || "未选择 Node"} · {activeAccount(client) || "未确认活动账号"} 冻结获准对象名单 → 预览确认 → 串行执行 → 查看逐项结果。消息发送仍需在 Windows 已登录会话完成真机验收。 }>范围约束任务 API 不自动重放等待 Web 验收
); } export function DiagnosticsView({ nodes, events, audit, selectedNodeId, onSelectNode }) { const visibleEvents = events.filter((event) => !selectedNodeId || event.node_id === selectedNodeId); const eventColumns = [ { title: "时间", dataIndex: "received_at", width: 180, render: formatTime }, { title: "Node", dataIndex: "node_id", width: 180 }, { title: "会话", dataIndex: "chat_id", render: (value) => shortId(value, 28) }, { title: "类型", dataIndex: "chat_type", width: 100, render: (value) => value === "Group" ? "群聊" : "私聊" }, { title: "授权内容", dataIndex: "content", ellipsis: true }, ]; const auditColumns = [ { title: "时间", dataIndex: "at", width: 180, render: formatTime }, { title: "操作者", dataIndex: "principal", width: 170 }, { title: "操作", dataIndex: "action", width: 180 }, { title: "资源", dataIndex: "resource" }, { title: "结果", dataIndex: "result", width: 100 }, ]; const nodeColumns = [ { title: "Node", dataIndex: "node_id" }, { title: "状态", dataIndex: "status", render: (value) => {statusLabel(value)} }, { title: "活动账号", dataIndex: "active_account_id", render: (value) => value || "—" }, { title: "最后心跳", dataIndex: "last_heartbeat_at", render: formatTime }, ]; return (
}, { key: "events", label: `授权事件 · ${visibleEvents.length}`, children:
}, { key: "audit", label: `操作审计 · ${audit.length}`, children:
}, ]} /> ); }