'use client'; import { useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { RefreshCw, Boxes, CheckCircle, AlertCircle, Clock, XCircle, StopCircle } from 'lucide-react'; interface AgentInstance { id: string; userId: string; name: string; agentType: string; poolServerId?: string; serverHost: string; hostPort: number; containerName: string; status: 'deploying' | 'running' | 'stopped' | 'error' | 'removed'; errorMessage?: string; config: Record; hasToken: boolean; createdAt: string; updatedAt: string; } const API = '/api/proxy/api/v1/agent/instances'; async function fetchInstances(): Promise { const res = await fetch(API); if (!res.ok) throw new Error('Failed to load instances'); return res.json(); } async function stopInstance(id: string): Promise { const res = await fetch(`${API}/${id}/stop`, { method: 'PUT' }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as any).message ?? 'Failed to stop instance'); } } async function removeInstance(id: string): Promise { const res = await fetch(`${API}/${id}`, { method: 'DELETE' }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as any).message ?? 'Failed to remove instance'); } } const STATUS_CONFIG = { deploying: { icon: , label: '部署中', color: 'text-blue-400' }, running: { icon: , label: '运行中', color: 'text-green-500' }, stopped: { icon: , label: '已停止', color: 'text-yellow-500' }, error: { icon: , label: '错误', color: 'text-red-500' }, removed: { icon: , label: '已移除', color: 'text-muted-foreground' }, }; function StatusBadge({ status }: { status: AgentInstance['status'] }) { const cfg = STATUS_CONFIG[status]; return (
{cfg.icon} {cfg.label}
); } function formatDate(iso: string) { return new Date(iso).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } export default function OpenClawInstancesPage() { const queryClient = useQueryClient(); const [statusFilter, setStatusFilter] = useState('all'); const { data: instances = [], isLoading, error, refetch } = useQuery({ queryKey: ['openclaw-instances'], queryFn: fetchInstances, refetchInterval: 15_000, // Auto-refresh every 15s to catch status changes }); const filtered = statusFilter === 'all' ? instances : instances.filter((i) => i.status === statusFilter); const counts = instances.reduce((acc, i) => { acc[i.status] = (acc[i.status] ?? 0) + 1; return acc; }, {} as Record); const handleStop = async (inst: AgentInstance) => { if (!confirm(`确定要停止实例「${inst.name}」吗?`)) return; try { await stopInstance(inst.id); toast.success('实例已停止'); queryClient.invalidateQueries({ queryKey: ['openclaw-instances'] }); } catch (err) { toast.error(err instanceof Error ? err.message : '操作失败'); } }; const handleRemove = async (inst: AgentInstance) => { if (!confirm(`确定要移除实例「${inst.name}」吗?容器将被销毁,数据卷保留。`)) return; try { await removeInstance(inst.id); toast.success('实例已移除'); queryClient.invalidateQueries({ queryKey: ['openclaw-instances'] }); } catch (err) { toast.error(err instanceof Error ? err.message : '移除失败'); } }; const FILTER_TABS: { value: AgentInstance['status'] | 'all'; label: string }[] = [ { value: 'all', label: `全部 (${instances.length})` }, { value: 'running', label: `运行中 (${counts.running ?? 0})` }, { value: 'deploying', label: `部署中 (${counts.deploying ?? 0})` }, { value: 'stopped', label: `已停止 (${counts.stopped ?? 0})` }, { value: 'error', label: `错误 (${counts.error ?? 0})` }, ]; return (
{/* Header */}

OpenClaw 实例

跨租户查看所有用户的 OpenClaw 智能体实例

{/* Status filter tabs */}
{FILTER_TABS.map(({ value, label }) => ( ))}
{/* Error */} {error && (
加载失败:{error instanceof Error ? error.message : String(error)}
)} {/* Loading */} {isLoading && (
{[1, 2, 3].map((i) => (
))}
)} {/* Instance list */} {!isLoading && !error && (
{filtered.length === 0 ? (

暂无实例数据

) : ( filtered.map((inst) => (
{inst.name} {inst.agentType}
容器: {inst.containerName} 地址: {inst.serverHost}:{inst.hostPort} 用户ID: {inst.userId.slice(0, 8)}… 创建: {formatDate(inst.createdAt)}
{inst.errorMessage && (

{inst.errorMessage}

)}
{/* Actions */} {inst.status !== 'removed' && (
{inst.status === 'running' && ( )} {(inst.status === 'stopped' || inst.status === 'error') && ( )}
)}
)) )}
)}
); }