Files
OpenFlare/frontend/app/(main)/nodes/detail/page-client.tsx
T
ryan 63cd906cfc refactor(repo): consolidate openflare-server to root and move subprojects to internal/apps
- Merge all files inside openflare-server to the repository root directory.
- Relocate agent, relay, and flared subprojects from internal/ to internal/apps/.
- Combine docker-compose files and update build context paths to root.
- Update GitHub workflows and Dockerfiles to refer to new directories and package names.
- Rewrite Go package imports across all files.
- Resolve database renew test race condition and clean up docs.
2026-06-19 14:23:29 +08:00

97 lines
2.7 KiB
TypeScript

'use client';
import Link from 'next/link';
import {useMemo} from 'react';
import {useSearchParams} from 'next/navigation';
import {useQuery} from '@tanstack/react-query';
import {ArrowLeft, Server} from 'lucide-react';
import {Button} from '@/components/ui/button';
import {EmptyStateWithBorder} from '@/components/layout/empty';
import {ErrorInline} from '@/components/layout/error';
import {LoadingStateWithBorder} from '@/components/layout/loading';
import {NodeService} from '@/lib/services/openflare';
import {EdgeNodeDetail} from '../components/edge-node-detail';
import {RelayNodeDetail} from '../components/relay-node-detail';
import {TunnelNodeDetail} from '../components/tunnel-node-detail';
import {getErrorMessage} from '../components/node-utils';
const nodesQueryKey = ['openflare', 'nodes'];
export function NodeDetailPageClient() {
const searchParams = useSearchParams();
const nodeId = searchParams.get('id')?.trim() ?? '';
const nodesQuery = useQuery({
queryKey: nodesQueryKey,
queryFn: () => NodeService.listNodes(),
refetchInterval: 5000,
});
const node = useMemo(() => {
if (!nodeId) return null;
return (nodesQuery.data ?? []).find((item) => String(item.id) === nodeId) ?? null;
}, [nodeId, nodesQuery.data]);
if (!nodeId) {
return (
<div className="py-6 px-1">
<EmptyStateWithBorder
icon={Server}
description="缺少节点 ID,请从节点列表进入详情页。"
/>
</div>
);
}
if (nodesQuery.isLoading) {
return (
<div className="py-6 px-1">
<LoadingStateWithBorder icon={Server} description="加载节点详情中..." />
</div>
);
}
if (nodesQuery.isError) {
return (
<div className="py-6 px-1">
<div className="p-8 border border-dashed rounded-lg">
<ErrorInline
message={getErrorMessage(nodesQuery.error)}
onRetry={() => void nodesQuery.refetch()}
className="justify-center"
/>
</div>
</div>
);
}
if (!node) {
return (
<div className="py-6 px-1 space-y-4">
<Button variant="ghost" size="sm" className="h-8 px-2" asChild>
<Link href="/nodes">
<ArrowLeft className="size-4 mr-1" />
返回节点列表
</Link>
</Button>
<EmptyStateWithBorder
icon={Server}
description="节点不存在,可能已被删除或 ID 无效。"
/>
</div>
);
}
if (node.node_type === 'tunnel_relay') {
return <RelayNodeDetail node={node} />;
}
if (node.node_type === 'tunnel_client') {
return <TunnelNodeDetail node={node} />;
}
return <EdgeNodeDetail node={node} />;
}