Skip to content

Commit 83e653c

Browse files
committed
feat(node-modal): enhance node data synchronization and form updates in NodeModal component
1 parent a0133ca commit 83e653c

File tree

1 file changed

+65
-2
lines changed

1 file changed

+65
-2
lines changed

dashboard/src/components/dialogs/node-modal.tsx

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
1313
import { Textarea } from '@/components/ui/textarea'
1414
import { queryClient } from '@/utils/query-client'
1515
import useDirDetection from '@/hooks/use-dir-detection'
16-
import React, { useState, useEffect } from 'react'
16+
import React, { useState, useEffect, useRef } from 'react'
1717
import { Loader2, Settings, RefreshCw } from 'lucide-react'
1818
import { v4 as uuidv4, v5 as uuidv5, v6 as uuidv6, v7 as uuidv7 } from 'uuid'
1919
import { LoaderButton } from '../ui/loader-button'
@@ -82,15 +82,75 @@ export default function NodeModal({ isDialogOpen, onOpenChange, form, editingNod
8282
)
8383

8484
const currentNode = node || initialNodeData
85+
const lastSyncedNodeRef = useRef<NodeResponse | null>(null)
86+
8587
useEffect(() => {
8688
if (isDialogOpen) {
8789
setErrorDetails(null)
8890
setAutoCheck(true)
8991
dataLimitInputRef.current = ''
9092
setIsFetchingNodeData(false)
93+
lastSyncedNodeRef.current = null
9194
}
9295
}, [isDialogOpen])
9396

97+
// Update form when node data changes (from auto-refresh or external updates)
98+
useEffect(() => {
99+
if (!isDialogOpen || !editingNode || !editingNodeId || !node) return
100+
101+
// Skip if form is dirty (user has made changes)
102+
if (form.formState.isDirty) return
103+
104+
// Skip if this is the same node data we already synced
105+
// Compare key fields that change externally (status, message, versions, usage)
106+
const lastSynced = lastSyncedNodeRef.current
107+
if (lastSynced &&
108+
lastSynced.id === node.id &&
109+
lastSynced.status === node.status &&
110+
lastSynced.message === node.message &&
111+
lastSynced.xray_version === node.xray_version &&
112+
lastSynced.node_version === node.node_version &&
113+
lastSynced.uplink === node.uplink &&
114+
lastSynced.downlink === node.downlink &&
115+
lastSynced.name === node.name &&
116+
lastSynced.address === node.address &&
117+
lastSynced.port === node.port) {
118+
return
119+
}
120+
121+
// Update form with new node data
122+
const dataLimitBytes = node.data_limit ?? null
123+
const dataLimitGB = dataLimitBytes !== null && dataLimitBytes !== undefined && dataLimitBytes > 0
124+
? dataLimitBytes / (1024 * 1024 * 1024)
125+
: 0
126+
127+
if (dataLimitGB > 0) {
128+
const formatted = parseFloat(dataLimitGB.toFixed(9))
129+
dataLimitInputRef.current = String(formatted)
130+
} else {
131+
dataLimitInputRef.current = ''
132+
}
133+
134+
form.reset({
135+
name: node.name,
136+
address: node.address,
137+
port: node.port,
138+
usage_coefficient: node.usage_coefficient,
139+
connection_type: node.connection_type,
140+
server_ca: node.server_ca,
141+
keep_alive: node.keep_alive,
142+
api_key: (node.api_key as string) || '',
143+
core_config_id: node.core_config_id ?? cores?.cores?.[0]?.id,
144+
data_limit: dataLimitGB,
145+
data_limit_reset_strategy: node.data_limit_reset_strategy ?? DataLimitResetStrategy.no_reset,
146+
reset_time: node.reset_time ?? null,
147+
default_timeout: node.default_timeout ?? 10,
148+
internal_timeout: node.internal_timeout ?? 15,
149+
}, { keepDirty: false, keepValues: false })
150+
151+
lastSyncedNodeRef.current = node
152+
}, [node, isDialogOpen, editingNode, editingNodeId, form, cores])
153+
94154
useEffect(() => {
95155
const values = form.getValues()
96156
const timer = setTimeout(() => {
@@ -145,6 +205,7 @@ export default function NodeModal({ isDialogOpen, onOpenChange, form, editingNod
145205
default_timeout: nodeData.default_timeout ?? 10,
146206
internal_timeout: nodeData.internal_timeout ?? 15,
147207
})
208+
lastSyncedNodeRef.current = nodeData
148209
setIsFetchingNodeData(false)
149210
} else {
150211
const fetchNodeData = async () => {
@@ -178,6 +239,7 @@ export default function NodeModal({ isDialogOpen, onOpenChange, form, editingNod
178239
default_timeout: nodeData.default_timeout ?? 10,
179240
internal_timeout: nodeData.internal_timeout ?? 15,
180241
})
242+
lastSyncedNodeRef.current = nodeData
181243
} catch (error) {
182244
console.error('Error fetching node data:', error)
183245
toast.error(t('nodes.fetchFailed'))
@@ -308,6 +370,7 @@ export default function NodeModal({ isDialogOpen, onOpenChange, form, editingNod
308370

309371
if (nodeId && editingNode) {
310372
queryClient.invalidateQueries({ queryKey: [`/api/node/${nodeId}`] })
373+
lastSyncedNodeRef.current = null
311374
}
312375
queryClient.invalidateQueries({ queryKey: ['/api/nodes'] })
313376
onOpenChange(false)
@@ -528,7 +591,7 @@ export default function NodeModal({ isDialogOpen, onOpenChange, form, editingNod
528591
}}
529592
/>
530593

531-
<Accordion type="single" collapsible className="mb-4 mt-0 w-full pb-4">
594+
<Accordion type="single" collapsible className="mb-4 !mt-0 w-full pb-4">
532595
<AccordionItem className="rounded-sm border px-4 [&_[data-state=closed]]:no-underline [&_[data-state=open]]:no-underline" value="advanced-settings">
533596
<AccordionTrigger>
534597
<div className="flex items-center gap-2">

0 commit comments

Comments
 (0)