Skip to content

Commit 26cf1e6

Browse files
committed
refactor(xray): streamline balancer, inbound, outbound, and routing sections
- Introduced `cloneBalancer`, `cloneInbound`, `cloneOutbound`, and `cloneRoutingRule` functions to facilitate deep cloning of objects. - Updated state management in `XrayBalancersSection`, `XrayInboundsSection`, `XrayOutboundsSection`, and `XrayRoutingSection` to simplify draft handling and improve performance. - Removed redundant checks for dialog mode when determining the current object state, enhancing code clarity. - Adjusted initial data handling in dialog components to ensure correct data is passed based on the current state. - Improved overall readability and maintainability of the code across multiple sections.
1 parent 8bff2b7 commit 26cf1e6

4 files changed

Lines changed: 59 additions & 35 deletions

File tree

dashboard/src/features/core-editor/components/xray/xray-balancers-section.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ function mergeBalancerPatch(balancer: RoutingBalancer, patch: Partial<RoutingBal
105105
return next as unknown as RoutingBalancer
106106
}
107107

108+
function cloneBalancer(balancer: RoutingBalancer): RoutingBalancer {
109+
return JSON.parse(JSON.stringify(balancer)) as RoutingBalancer
110+
}
111+
108112
function cloneJsonObject(value: JsonObject | undefined): JsonObject | undefined {
109113
return value === undefined ? undefined : (JSON.parse(JSON.stringify(value)) as JsonObject)
110114
}
@@ -251,6 +255,7 @@ export function XrayBalancersSection({ headerAddPulse, headerAddEpoch }: XrayBal
251255
const [detailOpen, setDetailOpen] = useState(false)
252256
const [dialogMode, setDialogMode] = useState<DialogMode>('edit')
253257
const [draftBalancer, setDraftBalancer] = useState<RoutingBalancer | null>(null)
258+
const [editOriginalBalancer, setEditOriginalBalancer] = useState<RoutingBalancer | null>(null)
254259
const [blockAddWhileDraftOpen, setBlockAddWhileDraftOpen] = useState(false)
255260
const [selectorCommitError, setSelectorCommitError] = useState<string | null>(null)
256261
const [observationTab, setObservationTab] = useState<ObservationTab>('observatory')
@@ -259,9 +264,9 @@ export function XrayBalancersSection({ headerAddPulse, headerAddEpoch }: XrayBal
259264
const balancers = profile?.routing?.balancers ?? []
260265

261266
const b = useMemo(() => {
262-
if (dialogMode === 'add' && draftBalancer) return draftBalancer
267+
if (draftBalancer) return draftBalancer
263268
return balancers[selected]
264-
}, [dialogMode, draftBalancer, balancers, selected])
269+
}, [draftBalancer, balancers, selected])
265270

266271
const balancerParityFields = useMemo(() => getGeneratedRoutingBalancerFields(), [])
267272
const strategyTypeLabel = useMemo(() => {
@@ -314,7 +319,7 @@ export function XrayBalancersSection({ headerAddPulse, headerAddEpoch }: XrayBal
314319
if (!detailOpen) return
315320
const p = profileRef.current
316321
if (!p) return
317-
const row = dialogMode === 'add' && draftBalancer ? draftBalancer : p.routing?.balancers?.[selected]
322+
const row = draftBalancer ?? p.routing?.balancers?.[selected]
318323
if (!row) return
319324
const next: Record<string, string> = {}
320325
for (const f of dialogScalarBalancerFields) {
@@ -398,6 +403,7 @@ export function XrayBalancersSection({ headerAddPulse, headerAddEpoch }: XrayBal
398403
setDetailOpen(false)
399404
setDialogMode('edit')
400405
setDraftBalancer(null)
406+
setEditOriginalBalancer(null)
401407
}
402408

403409
const handleDetailOpenChange = (open: boolean) => {
@@ -459,9 +465,14 @@ export function XrayBalancersSection({ headerAddPulse, headerAddEpoch }: XrayBal
459465
setDuplicateBalancerTagError(parsed.data.tag)
460466
return
461467
}
462-
if (balancerRequiresObservation(b)) {
463-
updateXrayProfile(p => ensureObservationForProfile(p, collectOutboundSelectors(p)))
464-
}
468+
const committed: RoutingBalancer = { ...b, tag: parsed.data.tag, selector: parsed.data.selector }
469+
updateXrayProfile(p => {
470+
const next = replaceBalancer(p, selected, committed)
471+
if (balancerRequiresObservation(committed)) {
472+
return ensureObservationForProfile(next, collectOutboundSelectors(next))
473+
}
474+
return next
475+
})
465476
finalizeDetailClose()
466477
}
467478

@@ -471,7 +482,7 @@ export function XrayBalancersSection({ headerAddPulse, headerAddEpoch }: XrayBal
471482
const sel = (patch.selector ?? []).map(s => String(s).trim()).filter(s => s.length > 0)
472483
if (sel.length > 0) setSelectorCommitError(null)
473484
}
474-
if (dialogMode === 'add' && draftBalancer !== null) {
485+
if (draftBalancer !== null) {
475486
setDraftBalancer(mergeBalancerPatch(draftBalancer, patch))
476487
return
477488
}
@@ -604,8 +615,10 @@ export function XrayBalancersSection({ headerAddPulse, headerAddEpoch }: XrayBal
604615
setBlockAddWhileDraftOpen(true)
605616
return
606617
}
607-
setDraftBalancer(null)
618+
const cloned = cloneBalancer(balancers[rowIndex])
619+
setDraftBalancer(cloned)
608620
setDialogMode('edit')
621+
setEditOriginalBalancer(cloneBalancer(cloned))
609622
setSelected(rowIndex)
610623
setDetailOpen(true)
611624
}}
@@ -649,8 +662,8 @@ export function XrayBalancersSection({ headerAddPulse, headerAddEpoch }: XrayBal
649662
<CoreEditorFormDialog
650663
isDialogOpen={detailOpen}
651664
onOpenChange={handleDetailOpenChange}
652-
initialData={dialogMode === 'add' ? initialDraftRef.current : null}
653-
getCurrentData={() => (dialogMode === 'add' ? draftBalancer : b)}
665+
initialData={dialogMode === 'add' ? initialDraftRef.current : editOriginalBalancer}
666+
getCurrentData={() => draftBalancer ?? b}
654667
discardTitle={dialogMode === 'add' ? t('coreEditor.balancer.discardDraftTitle', { defaultValue: 'Discard new balancer?' }) : t('coreEditor.balancer.discardDraftTitle', { defaultValue: 'Discard changes?' })}
655668
discardDescription={dialogMode === 'add' ? t('coreEditor.balancer.discardDraftDescription', { defaultValue: 'This balancer is not in the list yet. Closing without adding will discard your changes.' }) : t('coreEditor.balancer.discardDraftDescription', { defaultValue: 'Your modifications to this balancer will be lost if you close now.' })}
656669
discardActionLabel={t('coreEditor.balancer.discardDraftAction', { defaultValue: 'Discard' })}

dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,9 +1018,9 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
10181018

10191019
const inbound = useMemo(() => {
10201020
if (!profile) return undefined
1021-
if (dialogMode === 'add' && draftInbound) return draftInbound
1021+
if (draftInbound) return draftInbound
10221022
return profile.inbounds[selected]
1023-
}, [profile, dialogMode, draftInbound, selected])
1023+
}, [profile, draftInbound, selected])
10241024

10251025
const visibility = useMemo(() => (inbound ? getInboundFieldVisibility(inbound) : null), [inbound])
10261026
const caps = useMemo(() => getInboundFormCapabilities(), [])
@@ -1148,7 +1148,7 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
11481148
if (!detailOpen) return
11491149
const p = profileRef.current
11501150
if (!p) return
1151-
const row = dialogMode === 'add' && draftInbound ? draftInbound : p.inbounds[selected]
1151+
const row = draftInbound ?? p.inbounds[selected]
11521152
if (!row || row.protocol === 'unmanaged') return
11531153

11541154
const security = getInboundSecurityRecord(row)
@@ -1263,7 +1263,7 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
12631263
if (!detailOpen) return
12641264
const p = profileRef.current
12651265
if (!p) return
1266-
const row = dialogMode === 'add' && draftInbound ? draftInbound : p.inbounds[selected]
1266+
const row = draftInbound ?? p.inbounds[selected]
12671267
if (row && isTunnelInboundProtocol(row.protocol)) setTunnelBlankPortMapRows([])
12681268
}, [detailOpen, selected, dialogMode, draftInbound])
12691269

@@ -1831,7 +1831,7 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
18311831
nextInbound = vlessNext as Inbound
18321832
}
18331833

1834-
replaceEffectiveInbound(nextInbound)
1834+
updateXrayProfile(p => replaceInbound(p, selected, nextInbound))
18351835
if ('security' in nextInbound && nextInbound.security) form.setValue('security', nextInbound.security.type)
18361836
if ('transport' in nextInbound && nextInbound.transport) form.setValue('transport', nextInbound.transport.type)
18371837
if (nextInbound.protocol === 'vless') {
@@ -1843,7 +1843,7 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
18431843
}
18441844

18451845
const replaceEffectiveInbound = (next: Inbound) => {
1846-
if (dialogMode === 'add' && draftInbound !== null) setDraftInbound(next)
1846+
if (draftInbound !== null) setDraftInbound(next)
18471847
else updateXrayProfile(p => replaceInbound(p, selected, next))
18481848
}
18491849

@@ -1953,7 +1953,7 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
19531953
base.network = normalizeTunnelNetworkForKit(base.network)
19541954
}
19551955
const merged = base as Inbound
1956-
if (dialogMode === 'add' && draftInbound !== null) setDraftInbound(merged)
1956+
if (draftInbound !== null) setDraftInbound(merged)
19571957
else updateXrayProfile(p => replaceInbound(p, selected, merged))
19581958
}
19591959

@@ -2557,9 +2557,10 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
25572557
setBlockAddWhileDraftOpen(true)
25582558
return
25592559
}
2560-
setDraftInbound(null)
2560+
const cloned = cloneInbound(profile.inbounds[rowIndex])
2561+
setDraftInbound(cloned)
25612562
setDialogMode('edit')
2562-
setEditOriginalInbound(cloneInbound(profile.inbounds[rowIndex]))
2563+
setEditOriginalInbound(cloneInbound(cloned))
25632564
setSelected(rowIndex)
25642565
setDetailOpen(true)
25652566
}}
@@ -2613,7 +2614,7 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
26132614
: (editOriginalInbound ?? null)
26142615
}
26152616
getCurrentData={() => {
2616-
const cur = dialogMode === 'add' ? draftInbound : inbound
2617+
const cur = draftInbound ?? inbound
26172618
const copy = cur ? JSON.parse(JSON.stringify(cur)) : null
26182619
if (dialogMode === 'add' && copy) {
26192620
if (isTagAutoGenerated) delete copy.tag

dashboard/src/features/core-editor/components/xray/xray-outbounds-section.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -564,9 +564,9 @@ export function XrayOutboundsSection({ headerAddPulse, headerAddEpoch }: XrayOut
564564

565565
const ob = useMemo(() => {
566566
if (!profile) return undefined
567-
if (dialogMode === 'add' && draftOutbound) return draftOutbound
567+
if (draftOutbound) return draftOutbound
568568
return outbounds[selected]
569-
}, [profile, dialogMode, draftOutbound, outbounds, selected])
569+
}, [profile, draftOutbound, outbounds, selected])
570570

571571
const outboundCaps = useMemo(() => getOutboundFormCapabilities(), [])
572572
const outboundCapsRef = useRef(outboundCaps)
@@ -626,7 +626,7 @@ export function XrayOutboundsSection({ headerAddPulse, headerAddEpoch }: XrayOut
626626
if (!detailOpen) return
627627
const p = profileRef.current
628628
if (!p) return
629-
const row = dialogMode === 'add' && draftOutbound ? draftOutbound : p.outbounds?.[selected]
629+
const row = draftOutbound ?? p.outbounds?.[selected]
630630
if (!row || row.protocol === 'unmanaged') return
631631
form.reset(buildOutboundDetailFormValues(row as Outbound, outboundCapsRef.current))
632632
}, [detailOpen, selected, dialogMode, draftOutbound, settingsFormSeed, form])
@@ -692,7 +692,7 @@ export function XrayOutboundsSection({ headerAddPulse, headerAddEpoch }: XrayOut
692692

693693
const patchOutbound = (next: Outbound) => {
694694
const sanitized = sanitizeOutboundForState(next)
695-
if (dialogMode === 'add' && draftOutbound !== null) {
695+
if (draftOutbound !== null) {
696696
setDraftOutbound(sanitized)
697697
return
698698
}
@@ -879,9 +879,10 @@ export function XrayOutboundsSection({ headerAddPulse, headerAddEpoch }: XrayOut
879879
setBlockAddWhileDraftOpen(true)
880880
return
881881
}
882-
setDraftOutbound(null)
882+
const cloned = cloneOutbound(outbounds[rowIndex])
883+
setDraftOutbound(cloned)
883884
setDialogMode('edit')
884-
setEditOriginalOutbound(cloneOutbound(outbounds[rowIndex]))
885+
setEditOriginalOutbound(cloneOutbound(cloned))
885886
setSelected(rowIndex)
886887
setOutboundDialogTab('form')
887888
setUriDraft('')
@@ -928,9 +929,9 @@ export function XrayOutboundsSection({ headerAddPulse, headerAddEpoch }: XrayOut
928929
initialData={
929930
dialogMode === 'add'
930931
? { outbound: initialDraftRef.current, uriDraft: '', tab: 'form', json: '' }
931-
: { outbound: editOriginalOutbound ?? ob, uriDraft: '', tab: outboundDialogTab, json: outboundJsonText }
932+
: { outbound: editOriginalOutbound, uriDraft: '', tab: 'form', json: '' }
932933
}
933-
getCurrentData={() => ({ outbound: dialogMode === 'add' ? draftOutbound : ob, uriDraft, tab: outboundDialogTab, json: outboundJsonText })}
934+
getCurrentData={() => ({ outbound: draftOutbound ?? ob, uriDraft, tab: outboundDialogTab, json: outboundJsonText })}
934935
discardTitle={
935936
dialogMode === 'add' ? t('coreEditor.outbound.discardDraftTitle', { defaultValue: 'Discard new outbound?' }) : t('coreEditor.outbound.discardEditTitle', { defaultValue: 'Discard changes?' })
936937
}

dashboard/src/features/core-editor/components/xray/xray-routing-section.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ function routingRuleAsRecord(r: RoutingRule): Record<string, unknown> {
4646
return r as unknown as Record<string, unknown>
4747
}
4848

49+
function cloneRoutingRule(rule: RoutingRule): RoutingRule {
50+
return JSON.parse(JSON.stringify(rule)) as RoutingRule
51+
}
52+
4953
function mergeRoutingRulePatch(rule: RoutingRule, patch: Partial<RoutingRule>): RoutingRule {
5054
const next = { ...routingRuleAsRecord(rule) }
5155
for (const [key, value] of Object.entries(patch as Record<string, unknown>)) {
@@ -210,15 +214,16 @@ export function XrayRoutingSection({ headerAddPulse, headerAddEpoch }: XrayRouti
210214
const [detailOpen, setDetailOpen] = useState(false)
211215
const [dialogMode, setDialogMode] = useState<DialogMode>('edit')
212216
const [draftRule, setDraftRule] = useState<RoutingRule | null>(null)
217+
const [editOriginalRule, setEditOriginalRule] = useState<RoutingRule | null>(null)
213218
const [blockAddWhileDraftOpen, setBlockAddWhileDraftOpen] = useState(false)
214219
const [ruleDialogIssues, setRuleDialogIssues] = useState<Issue[]>([])
215220
const routing = profile?.routing ?? defaultRouting()
216221
const rules = routing.rules
217222

218223
const rule = useMemo(() => {
219-
if (dialogMode === 'add' && draftRule) return draftRule
224+
if (draftRule) return draftRule
220225
return rules[selected]
221-
}, [dialogMode, draftRule, rules, selected])
226+
}, [draftRule, rules, selected])
222227

223228
const routingCaps = useMemo(() => {
224229
const caps = getRoutingRuleFormCapabilities(profile ? { profile } : undefined)
@@ -268,7 +273,7 @@ export function XrayRoutingSection({ headerAddPulse, headerAddEpoch }: XrayRouti
268273
form.clearErrors('tag')
269274
const p = profileRef.current
270275
if (!p) return
271-
const r = dialogMode === 'add' && draftRule ? draftRule : p.routing?.rules?.[selected]
276+
const r = draftRule ?? p.routing?.rules?.[selected]
272277
if (!r) return
273278
const caps = routingCapsRef.current
274279
const next: Record<string, string> = {}
@@ -350,6 +355,7 @@ export function XrayRoutingSection({ headerAddPulse, headerAddEpoch }: XrayRouti
350355
setDetailOpen(false)
351356
setDialogMode('edit')
352357
setDraftRule(null)
358+
setEditOriginalRule(null)
353359
}
354360

355361
const handleDetailOpenChange = (open: boolean) => {
@@ -415,12 +421,13 @@ export function XrayRoutingSection({ headerAddPulse, headerAddEpoch }: XrayRouti
415421
return
416422
}
417423
setRuleDialogIssues([])
424+
updateXrayProfile(p => replaceRule(p, selected, rule))
418425
finalizeDetailClose()
419426
}
420427

421428
const patchRule = (patch: Partial<RoutingRule>) => {
422429
setRuleDialogIssues([])
423-
if (dialogMode === 'add' && draftRule !== null) {
430+
if (draftRule !== null) {
424431
setDraftRule(mergeRoutingRulePatch(draftRule, patch))
425432
return
426433
}
@@ -494,8 +501,10 @@ export function XrayRoutingSection({ headerAddPulse, headerAddEpoch }: XrayRouti
494501
setBlockAddWhileDraftOpen(true)
495502
return
496503
}
497-
setDraftRule(null)
504+
const cloned = cloneRoutingRule(rules[rowIndex])
505+
setDraftRule(cloned)
498506
setDialogMode('edit')
507+
setEditOriginalRule(cloneRoutingRule(cloned))
499508
setSelected(rowIndex)
500509
setDetailOpen(true)
501510
}}
@@ -527,8 +536,8 @@ export function XrayRoutingSection({ headerAddPulse, headerAddEpoch }: XrayRouti
527536
<CoreEditorFormDialog
528537
isDialogOpen={detailOpen}
529538
onOpenChange={handleDetailOpenChange}
530-
initialData={dialogMode === 'add' ? initialDraftRef.current : null}
531-
getCurrentData={() => (dialogMode === 'add' ? draftRule : rule)}
539+
initialData={dialogMode === 'add' ? initialDraftRef.current : editOriginalRule}
540+
getCurrentData={() => draftRule ?? rule}
532541
discardTitle={dialogMode === 'add' ? t('coreEditor.routing.discardDraftTitle', { defaultValue: 'Discard new rule?' }) : t('coreEditor.routing.discardEditTitle', { defaultValue: 'Discard changes?' })}
533542
discardDescription={dialogMode === 'add' ? t('coreEditor.routing.discardDraftDescription', { defaultValue: 'This rule is not in the list yet. Closing without adding will discard your changes.' }) : t('coreEditor.routing.discardDraftDescription', { defaultValue: 'Your modifications to this rule will be lost if you close now.' })}
534543
discardActionLabel={t('coreEditor.routing.discardDraftAction', { defaultValue: 'Discard' })}

0 commit comments

Comments
 (0)