diff --git a/frontend/src/api/queries/useOutboundTags.ts b/frontend/src/api/queries/useOutboundTags.ts new file mode 100644 index 000000000..102dea7f8 --- /dev/null +++ b/frontend/src/api/queries/useOutboundTags.ts @@ -0,0 +1,33 @@ +import { useQuery } from '@tanstack/react-query'; + +import { keys } from '@/api/queryKeys'; +import { fetchXrayConfig } from '@/hooks/useXraySetting'; + +// Available outbound (and balancer-eligible) tags the user can route an mtproto +// inbound's Telegram traffic to. Shares the cached xray config query so opening +// the inbound form costs no extra request when the Xray page was already +// visited; `select` derives just the tag list without disturbing other readers. +export function useOutboundTags() { + return useQuery({ + queryKey: keys.xray.config(), + queryFn: fetchXrayConfig, + staleTime: Infinity, + select: (data): string[] => { + const tags = new Set(); + for (const o of data?.xraySetting?.outbounds ?? []) { + const tag = (o as { tag?: string } | null)?.tag; + if (tag) tags.add(tag); + } + for (const t of data?.subscriptionOutboundTags ?? []) { + if (t) tags.add(t); + } + // Balancers are valid routing targets too — injectMtprotoEgress emits a + // balancerTag rule when the chosen tag names a balancer. + const balancers = (data?.xraySetting?.routing as { balancers?: Array<{ tag?: string }> } | undefined)?.balancers; + for (const b of balancers ?? []) { + if (b?.tag) tags.add(b.tag); + } + return [...tags]; + }, + }); +} diff --git a/frontend/src/hooks/useXraySetting.ts b/frontend/src/hooks/useXraySetting.ts index 8a024d5aa..9542b86cb 100644 --- a/frontend/src/hooks/useXraySetting.ts +++ b/frontend/src/hooks/useXraySetting.ts @@ -81,7 +81,7 @@ export interface UseXraySettingResult { type XrayConfigPayload = z.infer; -async function fetchXrayConfig(): Promise { +export async function fetchXrayConfig(): Promise { const msg = await HttpUtil.post('/panel/api/xray/', undefined, { silent: true }); if (!msg?.success) throw new Error(msg?.msg || 'Failed to load xray config'); if (typeof msg.obj !== 'string') throw new Error('Malformed xray config response: expected string'); diff --git a/frontend/src/pages/inbounds/form/protocols/mtproto.tsx b/frontend/src/pages/inbounds/form/protocols/mtproto.tsx index 0a435e7e5..378e796ac 100644 --- a/frontend/src/pages/inbounds/form/protocols/mtproto.tsx +++ b/frontend/src/pages/inbounds/form/protocols/mtproto.tsx @@ -3,10 +3,13 @@ import { Button, Form, Input, InputNumber, Select, Space, Switch } from 'antd'; import { ReloadOutlined } from '@ant-design/icons'; import { generateMtprotoSecret, mtprotoSecretForDomain } from '@/lib/xray/inbound-defaults'; +import { useOutboundTags } from '@/api/queries/useOutboundTags'; export default function MtprotoFields() { const { t } = useTranslation(); const form = Form.useFormInstance(); + const routeThroughXray = Form.useWatch(['settings', 'routeThroughXray'], form) as boolean | undefined; + const { data: outboundTags } = useOutboundTags(); return ( <> @@ -71,6 +74,28 @@ export default function MtprotoFields() { + + + + {routeThroughXray && ( + +