Skip to content

Commit a819e47

Browse files
committed
file:忘加的
1 parent cde079e commit a819e47

8 files changed

Lines changed: 2389 additions & 0 deletions

File tree

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
"use client";
2+
3+
import {
4+
Card,
5+
CardHeader,
6+
CardBody,
7+
Badge,
8+
Button,
9+
Chip,
10+
Divider,
11+
Dropdown,
12+
DropdownTrigger,
13+
DropdownMenu,
14+
DropdownItem,
15+
Tooltip,
16+
} from "@heroui/react";
17+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
18+
import {
19+
faServer,
20+
faDesktop,
21+
faCog,
22+
faEdit,
23+
faTrash,
24+
faEye,
25+
faArrowRight,
26+
faPlus,
27+
} from "@fortawesome/free-solid-svg-icons";
28+
import React from 'react';
29+
import { Flex, Box } from "@/components";
30+
import { TunnelGroup } from "@/lib/types";
31+
32+
interface Tunnel {
33+
id: string;
34+
name: string;
35+
type: string;
36+
tunnelAddress: string;
37+
tunnelPort: string;
38+
targetAddress: string;
39+
targetPort: string;
40+
status: {
41+
type: "success" | "danger" | "warning";
42+
text: string;
43+
};
44+
endpoint: string;
45+
}
46+
47+
interface GroupCardProps {
48+
group: TunnelGroup;
49+
tunnels: Tunnel[];
50+
onEdit?: (group: TunnelGroup) => void;
51+
onDelete?: (group: TunnelGroup) => void;
52+
onAddTunnels?: (group: TunnelGroup) => void;
53+
onViewDetails?: (group: TunnelGroup) => void;
54+
}
55+
56+
export const GroupCard: React.FC<GroupCardProps> = ({
57+
group,
58+
tunnels,
59+
onEdit,
60+
onDelete,
61+
onAddTunnels,
62+
onViewDetails,
63+
}) => {
64+
// 获取分组中的隧道
65+
const groupTunnels = tunnels.filter(tunnel =>
66+
group.tunnelIds.includes(tunnel.id)
67+
);
68+
69+
// 获取隧道类型标签
70+
const getTunnelTypeChip = (type: string) => {
71+
return (
72+
<Chip
73+
size="sm"
74+
variant="flat"
75+
color={type === '服务端' ? 'primary' : 'secondary'}
76+
>
77+
{type}
78+
</Chip>
79+
);
80+
};
81+
82+
// 获取状态颜色
83+
const getStatusDot = (status: { type: string }) => {
84+
const colorMap = {
85+
success: 'bg-success',
86+
danger: 'bg-danger',
87+
warning: 'bg-warning',
88+
};
89+
return colorMap[status.type as keyof typeof colorMap] || 'bg-default';
90+
};
91+
92+
// 统计不同类型的隧道数量
93+
const tunnelStats = groupTunnels.reduce((acc, tunnel) => {
94+
const type = tunnel.type;
95+
acc[type] = (acc[type] || 0) + 1;
96+
return acc;
97+
}, {} as Record<string, number>);
98+
99+
// 统计运行中的隧道数量
100+
const runningCount = groupTunnels.filter(t => t.status.type === 'success').length;
101+
102+
return (
103+
<Card className="hover:shadow-lg transition-all duration-200 border-l-4"
104+
style={{ borderLeftColor: group.color }}>
105+
<CardHeader>
106+
<Flex className="items-center justify-between w-full">
107+
<Flex className="items-center gap-3">
108+
<div
109+
className="w-4 h-4 rounded-full shadow-sm"
110+
style={{ backgroundColor: group.color }}
111+
/>
112+
<Box>
113+
<h3 className="font-semibold text-lg">{group.name}</h3>
114+
{group.description && (
115+
<p className="text-sm text-default-500 mt-1">{group.description}</p>
116+
)}
117+
</Box>
118+
</Flex>
119+
120+
<Dropdown>
121+
<DropdownTrigger>
122+
<Button
123+
isIconOnly
124+
size="sm"
125+
variant="light"
126+
className="text-default-400 hover:text-default-600"
127+
>
128+
<FontAwesomeIcon icon={faCog} />
129+
</Button>
130+
</DropdownTrigger>
131+
<DropdownMenu>
132+
<DropdownItem
133+
key="view"
134+
startContent={<FontAwesomeIcon icon={faEye} />}
135+
onPress={() => onViewDetails?.(group)}
136+
>
137+
查看详情
138+
</DropdownItem>
139+
<DropdownItem
140+
key="add"
141+
startContent={<FontAwesomeIcon icon={faPlus} />}
142+
onPress={() => onAddTunnels?.(group)}
143+
>
144+
添加隧道
145+
</DropdownItem>
146+
<DropdownItem
147+
key="edit"
148+
startContent={<FontAwesomeIcon icon={faEdit} />}
149+
onPress={() => onEdit?.(group)}
150+
>
151+
编辑分组
152+
</DropdownItem>
153+
<DropdownItem
154+
key="delete"
155+
className="text-danger"
156+
color="danger"
157+
startContent={<FontAwesomeIcon icon={faTrash} />}
158+
onPress={() => onDelete?.(group)}
159+
>
160+
删除分组
161+
</DropdownItem>
162+
</DropdownMenu>
163+
</Dropdown>
164+
</Flex>
165+
</CardHeader>
166+
167+
<Divider />
168+
169+
<CardBody>
170+
<Box className="space-y-4">
171+
{/* 统计信息 */}
172+
<Flex className="items-center gap-4">
173+
<Badge content={group.tunnelIds.length} color="primary">
174+
<FontAwesomeIcon icon={faServer} className="text-default-400" />
175+
</Badge>
176+
<span className="text-sm text-default-500">
177+
{group.tunnelIds.length} 个隧道
178+
</span>
179+
180+
{runningCount > 0 && (
181+
<>
182+
<div className="w-1 h-1 bg-default-300 rounded-full" />
183+
<span className="text-sm text-success">
184+
{runningCount} 个运行中
185+
</span>
186+
</>
187+
)}
188+
</Flex>
189+
190+
{/* 隧道类型统计 */}
191+
{Object.keys(tunnelStats).length > 0 && (
192+
<Flex className="gap-2 flex-wrap">
193+
{Object.entries(tunnelStats).map(([type, count]) => (
194+
<Chip key={type} size="sm" variant="flat" color="default">
195+
{type}: {count}
196+
</Chip>
197+
))}
198+
</Flex>
199+
)}
200+
201+
{/* 隧道列表预览 */}
202+
{groupTunnels.length > 0 ? (
203+
<Box className="space-y-2">
204+
<h4 className="text-sm font-medium text-default-700">隧道列表</h4>
205+
<Box className="space-y-2 max-h-40 overflow-y-auto">
206+
{groupTunnels.slice(0, 5).map(tunnel => (
207+
<Flex
208+
key={tunnel.id}
209+
className="items-center gap-3 p-2 bg-default-50 rounded-lg hover:bg-default-100 transition-colors"
210+
>
211+
<div className={`w-2 h-2 rounded-full ${getStatusDot(tunnel.status)}`} />
212+
<FontAwesomeIcon
213+
icon={tunnel.type === '服务端' ? faServer : faDesktop}
214+
className="text-default-400 text-sm"
215+
/>
216+
<span className="text-sm font-medium flex-1 truncate">
217+
{tunnel.name}
218+
</span>
219+
{getTunnelTypeChip(tunnel.type)}
220+
</Flex>
221+
))}
222+
223+
{groupTunnels.length > 5 && (
224+
<div className="text-center py-2">
225+
<span className="text-xs text-default-400">
226+
还有 {groupTunnels.length - 5} 个隧道...
227+
</span>
228+
</div>
229+
)}
230+
</Box>
231+
</Box>
232+
) : (
233+
<Box className="text-center py-6">
234+
<FontAwesomeIcon icon={faServer} className="text-3xl text-default-300 mb-2" />
235+
<p className="text-sm text-default-500">该分组暂无隧道</p>
236+
<Button
237+
size="sm"
238+
color="primary"
239+
variant="flat"
240+
className="mt-2"
241+
startContent={<FontAwesomeIcon icon={faPlus} />}
242+
onPress={() => onAddTunnels?.(group)}
243+
>
244+
添加隧道
245+
</Button>
246+
</Box>
247+
)}
248+
249+
{/* 连接关系示例(针对双端/穿透类型) */}
250+
{groupTunnels.length >= 2 && (
251+
<Box className="border-t pt-3">
252+
<h4 className="text-sm font-medium text-default-700 mb-2">连接关系</h4>
253+
<Box className="bg-gradient-to-r from-primary-50 to-secondary-50 p-3 rounded-lg">
254+
<Flex className="items-center gap-2 text-sm">
255+
<span className="font-medium">{groupTunnels[0]?.name}</span>
256+
<FontAwesomeIcon icon={faArrowRight} className="text-default-400" />
257+
<span className="font-medium">{groupTunnels[1]?.name}</span>
258+
</Flex>
259+
<p className="text-xs text-default-500 mt-1">
260+
双端连接示例
261+
</p>
262+
</Box>
263+
</Box>
264+
)}
265+
</Box>
266+
</CardBody>
267+
</Card>
268+
);
269+
};

0 commit comments

Comments
 (0)