@@ -2,12 +2,11 @@ import { useState } from 'react'
22import { Card } from '../ui/card'
33import { DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuTrigger , DropdownMenuSeparator } from '../ui/dropdown-menu'
44import { Button } from '../ui/button'
5- import { MoreVertical , Pencil , Trash2 , Power , Activity , RotateCcw , Wifi , Loader2 , RefreshCw , Download , Package , Server , AlertCircle , CheckCircle2 , Clock , XCircle , Link2 , Map } from 'lucide-react'
5+ import { MoreVertical , Pencil , Trash2 , Power , Activity , RotateCcw , Wifi , Loader2 , RefreshCw , Download , Package , Server , AlertCircle , Link2 , Map } from 'lucide-react'
66import { useTranslation } from 'react-i18next'
77import useDirDetection from '@/hooks/use-dir-detection'
88import { AlertDialog , AlertDialogAction , AlertDialogCancel , AlertDialogContent , AlertDialogDescription , AlertDialogFooter , AlertDialogHeader , AlertDialogTitle } from '@/components/ui/alert-dialog'
99import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from '@/components/ui/tooltip'
10- import { Badge } from '@/components/ui/badge'
1110import { Separator } from '@/components/ui/separator'
1211import { cn } from '@/lib/utils'
1312import { toast } from 'sonner'
@@ -213,56 +212,60 @@ export default function Node({ node, onEdit, onToggleStatus }: NodeProps) {
213212 switch ( node . status ) {
214213 case 'connected' :
215214 return {
216- icon : CheckCircle2 ,
217215 label : t ( 'nodeModal.status.connected' , { defaultValue : 'Connected' } ) ,
218- className : 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 border-emerald-500/20' ,
219- dotColor : 'bg-emerald-500' ,
220216 }
221217 case 'connecting' :
222218 return {
223- icon : Clock ,
224219 label : t ( 'nodeModal.status.connecting' , { defaultValue : 'Connecting' } ) ,
225- className : 'bg-amber-500/10 text-amber-700 dark:text-amber-400 border-amber-500/20' ,
226- dotColor : 'bg-amber-500' ,
227220 }
228221 case 'error' :
229222 return {
230- icon : XCircle ,
231223 label : t ( 'nodeModal.status.error' , { defaultValue : 'Error' } ) ,
232- className : 'bg-destructive/10 text-destructive border-destructive/20' ,
233- dotColor : 'bg-destructive' ,
234224 }
235225 case 'limited' :
236226 return {
237- icon : AlertCircle ,
238227 label : t ( 'status.limited' , { defaultValue : 'Limited' } ) ,
239- className : 'bg-orange-500/10 text-orange-700 dark:text-orange-400 border-orange-500/20' ,
240- dotColor : 'bg-orange-500' ,
241228 }
242229 default :
243230 return {
244- icon : XCircle ,
245231 label : t ( 'nodeModal.status.disabled' , { defaultValue : 'Disabled' } ) ,
246- className : 'bg-muted text-muted-foreground border-border' ,
247- dotColor : 'bg-muted-foreground/50' ,
248232 }
249233 }
250234 }
251235
252236 const statusConfig = getStatusConfig ( )
253- const StatusIcon = statusConfig . icon
237+
238+ const getStatusDotColor = ( ) => {
239+ switch ( node . status ) {
240+ case 'connected' :
241+ return 'bg-green-500'
242+ case 'connecting' :
243+ return 'bg-amber-500'
244+ case 'error' :
245+ return 'bg-destructive'
246+ case 'limited' :
247+ return 'bg-orange-500'
248+ default :
249+ return 'bg-gray-400 dark:bg-gray-600'
250+ }
251+ }
254252
255253 return (
256254 < TooltipProvider >
257255 < Card className = "group relative h-full cursor-pointer overflow-hidden border transition-colors hover:bg-accent" onClick = { ( ) => onEdit ( node ) } >
258- { /* Status accent bar */ }
259- < div className = { cn ( 'absolute inset-x-0 top-0 h-0.5 sm:h-1' , statusConfig . dotColor ) } />
260-
261256 < div className = "p-3" >
262257 { /* Header */ }
263- < div className = "mb-2 flex items-start justify-between gap-2" >
258+ < div className = "flex items-start justify-between gap-2" >
264259 < div className = "min-w-0 flex-1" >
265- < div className = "mb-1.5 flex items-center gap-1.5" >
260+ < div className = "mb-0.5 flex items-center gap-1.5" >
261+ < Tooltip >
262+ < TooltipTrigger asChild >
263+ < div className = { cn ( 'h-2 w-2 rounded-full shrink-0' , getStatusDotColor ( ) ) } />
264+ </ TooltipTrigger >
265+ < TooltipContent >
266+ < p > { statusConfig . label } </ p >
267+ </ TooltipContent >
268+ </ Tooltip >
266269 < h3 className = "truncate text-sm sm:text-base font-semibold leading-tight tracking-tight" > { node . name } </ h3 >
267270 { node . status === 'error' && node . message ? (
268271 < Tooltip >
@@ -275,27 +278,6 @@ export default function Node({ node, onEdit, onToggleStatus }: NodeProps) {
275278 </ Tooltip >
276279 ) : null }
277280 </ div >
278- < div className = "flex items-center gap-1.5" >
279- < Tooltip >
280- < TooltipTrigger asChild >
281- < Badge variant = "outline" className = { cn ( 'text-[9px] sm:text-[10px] font-medium transition-colors flex items-center gap-1' , statusConfig . className ) } >
282- < StatusIcon className = { cn ( 'h-2.5 w-2.5 sm:h-3 sm:w-3 shrink-0' , dir === 'rtl' ? 'ml-0.5' : 'mr-0.5' ) } />
283- { statusConfig . label }
284- </ Badge >
285- </ TooltipTrigger >
286- < TooltipContent className = "max-w-xs" >
287- < div className = "space-y-2 text-xs" >
288- < div className = "font-semibold" > { t ( 'node.status' , { defaultValue : 'Node Status' } ) } </ div >
289- < div className = "space-y-1.5" >
290- < div className = "flex items-center justify-between gap-4" >
291- < span > { t ( 'status' , { defaultValue : 'Status' } ) } </ span >
292- < span className = "font-medium" > { statusConfig . label } </ span >
293- </ div >
294- </ div >
295- </ div >
296- </ TooltipContent >
297- </ Tooltip >
298- </ div >
299281 </ div >
300282 < div onClick = { e => e . stopPropagation ( ) } >
301283 < DropdownMenu >
@@ -425,8 +407,8 @@ export default function Node({ node, onEdit, onToggleStatus }: NodeProps) {
425407 'group/version inline-flex items-center rounded-md border px-1.5 py-0.5 sm:px-2 sm:py-1 transition-all cursor-pointer' ,
426408 dir === 'rtl' ? 'flex-row-reverse gap-1' : 'gap-1' ,
427409 latestXrayVersion && hasXrayUpdate ( node . xray_version )
428- ? 'border-amber-500/50 bg-amber-500/10 hover:border-amber-500/70 hover:bg-amber-500/15'
429- : 'border-border/50 bg-muted/40 hover:border-border hover:bg-muted/60 ' ,
410+ ? 'border-amber-500/50 bg-amber-500/10 group- hover:border-amber-500/70 group- hover:bg-amber-500/15'
411+ : 'border-border/50 bg-background/50 group- hover:border-border group- hover:bg-background/80 ' ,
430412 ) }
431413 onClick = { e => {
432414 e . stopPropagation ( )
@@ -438,9 +420,9 @@ export default function Node({ node, onEdit, onToggleStatus }: NodeProps) {
438420 { node . xray_version }
439421 </ span >
440422 { latestXrayVersion && hasXrayUpdate ( node . xray_version ) && (
441- < div className = { cn ( 'flex items-center' , dir === 'rtl' ? 'flex-row-reverse gap-0.5 ' : 'gap-0.5 ' ) } >
442- < div className = "h-1 w-1 sm:h-1 .5 sm: w-1.5 rounded-full bg-amber-500 animate-pulse " />
443- < Download className = "h-2.5 w-2.5 sm:h-3 sm:w-3 text-amber-600 dark:text-amber-400" />
423+ < div className = { cn ( 'flex items-center gap-0.5 ' , dir === 'rtl' ? 'flex-row-reverse' : '' ) } >
424+ < div className = "h-1.5 w-1.5 rounded-full bg-amber-500 shrink-0 " />
425+ < Download className = "h-2.5 w-2.5 text-amber-600 dark:text-amber-400 shrink-0 " />
444426 </ div >
445427 ) }
446428 </ div >
@@ -478,18 +460,18 @@ export default function Node({ node, onEdit, onToggleStatus }: NodeProps) {
478460 'group/version inline-flex items-center rounded-md border px-1.5 py-0.5 sm:px-2 sm:py-1 transition-all' ,
479461 dir === 'rtl' ? 'flex-row-reverse gap-1' : 'gap-1' ,
480462 latestNodeVersion && hasNodeUpdate ( node . node_version )
481- ? 'border-amber-500/50 bg-amber-500/10'
482- : 'border-border/50 bg-muted/40 ' ,
463+ ? 'border-amber-500/50 bg-amber-500/10 group-hover:border-amber-500/70 group-hover:bg-amber-500/15 '
464+ : 'border-border/50 bg-background/50 group-hover:border-border group-hover:bg-background/80 ' ,
483465 ) }
484466 >
485467 < Server className = { cn ( 'h-3 w-3 sm:h-3.5 sm:w-3.5 shrink-0 transition-colors' , latestNodeVersion && hasNodeUpdate ( node . node_version ) ? 'text-amber-600 dark:text-amber-400' : 'text-muted-foreground' ) } />
486468 < span className = { cn ( 'text-[10px] sm:text-[11px] font-medium font-mono' , latestNodeVersion && hasNodeUpdate ( node . node_version ) ? 'text-amber-700 dark:text-amber-300' : 'text-foreground' ) } >
487469 { node . node_version }
488470 </ span >
489471 { latestNodeVersion && hasNodeUpdate ( node . node_version ) && (
490- < div className = { cn ( 'flex items-center' , dir === 'rtl' ? 'flex-row-reverse gap-0.5 ' : 'gap-0.5 ' ) } >
491- < div className = "h-1 w-1 sm:h-1 .5 sm: w-1.5 rounded-full bg-amber-500 animate-pulse " />
492- < Download className = "h-2.5 w-2.5 sm:h-3 sm:w-3 text-amber-600 dark:text-amber-400" />
472+ < div className = { cn ( 'flex items-center gap-0.5 ' , dir === 'rtl' ? 'flex-row-reverse' : '' ) } >
473+ < div className = "h-1.5 w-1.5 rounded-full bg-amber-500 shrink-0 " />
474+ < Download className = "h-2.5 w-2.5 text-amber-600 dark:text-amber-400 shrink-0 " />
493475 </ div >
494476 ) }
495477 </ div >
0 commit comments