@@ -6,6 +6,10 @@ import NodeActionsMenu from '@/features/nodes/components/node-actions-menu'
66import { CoresSimpleResponse , NodeResponse , NodeStatus } from '@/service/api'
77import { cn } from '@/lib/utils'
88import { Package , Server } from 'lucide-react'
9+ import { useXrayReleases } from '@/hooks/use-xray-releases'
10+ import { useNodeReleases } from '@/hooks/use-node-releases'
11+ import { Separator } from '@/components/ui/separator'
12+ import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from '@/components/ui/tooltip'
913
1014interface UseNodeListColumnsProps {
1115 onEdit : ( node : NodeResponse ) => void
@@ -35,6 +39,8 @@ const getNodeStatusDotColor = (status: NodeStatus) => {
3539
3640export const useNodeListColumns = ( { onEdit, onToggleStatus, coresData, canUpdate = true , canDelete = true , canReconnect = true , canUpdateCore = true , canReadStats = true } : UseNodeListColumnsProps ) => {
3741 const { t } = useTranslation ( )
42+ const { latestVersion : latestXrayVersion , hasUpdate : hasXrayUpdate } = useXrayReleases ( )
43+ const { latestVersion : latestNodeVersion , hasUpdate : hasNodeUpdate } = useNodeReleases ( )
3844
3945 return useMemo < ListColumn < NodeResponse > [ ] > (
4046 ( ) => [
@@ -65,22 +71,89 @@ export const useNodeListColumns = ({ onEdit, onToggleStatus, coresData, canUpdat
6571 header : t ( 'version.title' , { defaultValue : 'Version' } ) ,
6672 width : '2fr' ,
6773 cell : node => {
68- if ( ! node . xray_version && ! node . node_version ) return null
74+ const coreVersion = node . core_version ?? node . xray_version
75+ const resolvedCoreType = coresData ?. cores ?. find ( c => c . id === node . core_config_id ) ?. type ?? null
76+ const isWireGuardCore = resolvedCoreType === 'wg'
77+ const isXrayBackend = resolvedCoreType !== 'wg'
78+ const coreUpdateVersion = node . xray_version ?? coreVersion
79+ const hasCoreUpdate = ! ! ( isXrayBackend && coreUpdateVersion && latestXrayVersion && hasXrayUpdate ( coreUpdateVersion ) )
80+ const hasNodeVersionUpdate = ! isWireGuardCore && ! ! latestNodeVersion && ! ! node . node_version && hasNodeUpdate ( node . node_version )
81+
82+ if ( ! coreVersion && ! node . node_version ) return null
83+
6984 return (
70- < div className = "text-muted-foreground flex flex-col gap-1 text-xs" >
71- { node . xray_version && (
72- < div className = "flex items-center gap-1.5" >
73- < Server className = "h-3.5 w-3.5 shrink-0" />
74- < span className = "truncate" > { node . xray_version } </ span >
75- </ div >
76- ) }
77- { node . node_version && (
78- < div className = "flex items-center gap-1.5" >
79- < Package className = "h-3.5 w-3.5 shrink-0" />
80- < span className = "truncate" > { node . node_version } </ span >
81- </ div >
82- ) }
83- </ div >
85+ < TooltipProvider >
86+ < div className = "flex flex-col gap-1 text-xs" >
87+ { coreVersion && (
88+ < Tooltip >
89+ < TooltipTrigger asChild >
90+ < div className = "inline-flex min-w-0 items-center gap-1.5" >
91+ < Package className = { cn ( 'h-3.5 w-3.5 shrink-0 transition-colors' , hasCoreUpdate ? 'text-amber-600 dark:text-amber-400' : 'text-muted-foreground' ) } />
92+ < span className = { cn ( 'truncate font-mono font-medium' , hasCoreUpdate ? 'text-amber-700 dark:text-amber-300' : 'text-muted-foreground' ) } > { coreVersion } </ span >
93+ { hasCoreUpdate && < span className = "h-1.5 w-1.5 shrink-0 rounded-full bg-amber-500" /> }
94+ </ div >
95+ </ TooltipTrigger >
96+ < TooltipContent side = "top" className = "max-w-xs" >
97+ < div className = "space-y-2 text-xs" >
98+ < div className = "font-semibold" > { t ( 'node.xrayVersion' , { defaultValue : 'Core' } ) } </ div >
99+ < div className = "space-y-1.5" >
100+ < div className = "flex items-center justify-between gap-4" >
101+ < span > { t ( 'version.currentVersion' , { defaultValue : 'Current' } ) } </ span >
102+ < span className = "font-mono font-medium" > { coreVersion } </ span >
103+ </ div >
104+ { isXrayBackend && latestXrayVersion && (
105+ < div className = "flex items-center justify-between gap-4" >
106+ < span > { t ( 'version.latestVersion' , { defaultValue : 'Latest' } ) } </ span >
107+ < span className = "font-mono font-medium" > { latestXrayVersion } </ span >
108+ </ div >
109+ ) }
110+ { hasCoreUpdate && (
111+ < >
112+ < Separator className = "my-1.5" />
113+ < span > { t ( 'nodeModal.updateAvailable' , { defaultValue : 'Update available' } ) } </ span >
114+ </ >
115+ ) }
116+ </ div >
117+ </ div >
118+ </ TooltipContent >
119+ </ Tooltip >
120+ ) }
121+ { node . node_version && (
122+ < Tooltip >
123+ < TooltipTrigger asChild >
124+ < div className = "inline-flex min-w-0 items-center gap-1.5" >
125+ < Server className = { cn ( 'h-3.5 w-3.5 shrink-0 transition-colors' , hasNodeVersionUpdate ? 'text-amber-600 dark:text-amber-400' : 'text-muted-foreground' ) } />
126+ < span className = { cn ( 'truncate font-mono font-medium' , hasNodeVersionUpdate ? 'text-amber-700 dark:text-amber-300' : 'text-muted-foreground' ) } > { node . node_version } </ span >
127+ { hasNodeVersionUpdate && < span className = "h-1.5 w-1.5 shrink-0 rounded-full bg-amber-500" /> }
128+ </ div >
129+ </ TooltipTrigger >
130+ < TooltipContent side = "top" className = "max-w-xs" >
131+ < div className = "space-y-2 text-xs" >
132+ < div className = "font-semibold" > { t ( 'node.coreVersion' , { defaultValue : 'Node Core' } ) } </ div >
133+ < div className = "space-y-1.5" >
134+ < div className = "flex items-center justify-between gap-4" >
135+ < span > { t ( 'version.currentVersion' , { defaultValue : 'Current' } ) } </ span >
136+ < span className = "font-mono font-medium" > { node . node_version } </ span >
137+ </ div >
138+ { ! isWireGuardCore && latestNodeVersion && (
139+ < div className = "flex items-center justify-between gap-4" >
140+ < span > { t ( 'version.latestVersion' , { defaultValue : 'Latest' } ) } </ span >
141+ < span className = "font-mono font-medium" > { latestNodeVersion } </ span >
142+ </ div >
143+ ) }
144+ { hasNodeVersionUpdate && (
145+ < >
146+ < Separator className = "my-1.5" />
147+ < span > { t ( 'nodeModal.updateAvailable' , { defaultValue : 'Update available' } ) } </ span >
148+ </ >
149+ ) }
150+ </ div >
151+ </ div >
152+ </ TooltipContent >
153+ </ Tooltip >
154+ ) }
155+ </ div >
156+ </ TooltipProvider >
84157 )
85158 } ,
86159 hideOnMobile : true ,
@@ -118,6 +191,6 @@ export const useNodeListColumns = ({ onEdit, onToggleStatus, coresData, canUpdat
118191 ]
119192 : [ ] ) ,
120193 ] ,
121- [ t , onEdit , onToggleStatus , coresData , canUpdate , canDelete , canReconnect , canUpdateCore , canReadStats ] ,
194+ [ t , onEdit , onToggleStatus , coresData , canUpdate , canDelete , canReconnect , canUpdateCore , canReadStats , latestXrayVersion , hasXrayUpdate , latestNodeVersion , hasNodeUpdate ] ,
122195 )
123196}
0 commit comments