grabbit 13ce6ae442 feat: implement complete edge device registration system
- Add hardware fingerprinting with cross-platform support
- Implement secure device registration flow with X.509 certificates
- Add WebSocket real-time communication for device status
- Create comprehensive device management dashboard
- Establish zero-trust security architecture with multi-layer protection
- Add database migrations for device registration entities
- Implement Rust edge client with hardware identification
- Add certificate management and automated provisioning system

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-13 08:46:25 +08:00

183 lines
5.2 KiB
TypeScript

import React from 'react'
import {
MoreVertical,
Wifi,
WifiOff,
Settings,
Trash2,
Edit,
Activity,
Calendar
} from 'lucide-react'
import { formatDistanceToNow } from 'date-fns'
import { zhCN } from 'date-fns/locale'
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
import { Button } from '../ui/button'
import { Badge } from '../ui/badge'
import { StatusIndicator } from '../ui/status-indicator'
import { DeviceDto, DeviceStatus } from '../../types/device'
interface DeviceCardProps {
device: DeviceDto
onEdit?: (device: DeviceDto) => void
onDelete?: (device: DeviceDto) => void
onConfigure?: (device: DeviceDto) => void
className?: string
}
export function DeviceCard({
device,
onEdit,
onDelete,
onConfigure,
className
}: DeviceCardProps) {
const getStatusConfig = (status: DeviceStatus) => {
switch (status) {
case DeviceStatus.ONLINE:
return {
variant: 'success' as const,
icon: <Wifi className="w-3 h-3" />,
text: '在线'
}
case DeviceStatus.OFFLINE:
return {
variant: 'destructive' as const,
icon: <WifiOff className="w-3 h-3" />,
text: '离线'
}
case DeviceStatus.ACTIVE:
return {
variant: 'success' as const,
icon: <Activity className="w-3 h-3" />,
text: '活跃'
}
case DeviceStatus.INACTIVE:
return {
variant: 'secondary' as const,
icon: <Activity className="w-3 h-3" />,
text: '非活跃'
}
case DeviceStatus.MAINTENANCE:
return {
variant: 'warning' as const,
icon: <Settings className="w-3 h-3" />,
text: '维护中'
}
default:
return {
variant: 'secondary' as const,
icon: <Activity className="w-3 h-3" />,
text: '未知'
}
}
}
const statusConfig = getStatusConfig(device.status)
const getLastSeenText = () => {
if (!device.lastSeenAt) {
return '从未连接'
}
return formatDistanceToNow(new Date(device.lastSeenAt), { addSuffix: true, locale: zhCN })
}
const getRegisteredText = () => {
return formatDistanceToNow(new Date(device.registeredAt), { addSuffix: true, locale: zhCN })
}
return (
<Card className={`hover:shadow-md transition-shadow ${className}`}>
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<CardTitle className="text-lg truncate">
{device.deviceName || '未命名设备'}
</CardTitle>
<div className="flex items-center space-x-2 mt-1">
<Badge variant={statusConfig.variant} className="flex items-center space-x-1">
{statusConfig.icon}
<span>{statusConfig.text}</span>
</Badge>
<StatusIndicator
status={device.status === DeviceStatus.ONLINE ? 'online' : 'offline'}
size="sm"
/>
</div>
</div>
<div className="relative">
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
>
<MoreVertical className="w-4 h-4" />
</Button>
{/* Dropdown menu would go here in a real implementation */}
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Device Details */}
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">ID</span>
<span className="font-mono text-xs">{device.hardwareId}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span>{getLastSeenText()}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span>{getRegisteredText()}</span>
</div>
</div>
{/* Action Buttons */}
<div className="flex space-x-2 pt-2">
{onConfigure && (
<Button
variant="outline"
size="sm"
onClick={() => onConfigure(device)}
className="flex-1"
>
<Settings className="w-4 h-4 mr-2" />
</Button>
)}
{onEdit && (
<Button
variant="outline"
size="sm"
onClick={() => onEdit(device)}
className="flex-1"
>
<Edit className="w-4 h-4 mr-2" />
</Button>
)}
</div>
{onDelete && (
<Button
variant="outline"
size="sm"
onClick={() => onDelete(device)}
className="w-full text-red-600 border-red-200 hover:bg-red-50"
>
<Trash2 className="w-4 h-4 mr-2" />
</Button>
)}
</CardContent>
</Card>
)
}