diff --git a/frontend/src/pages/xray/OutboundFormModal.vue b/frontend/src/pages/xray/OutboundFormModal.vue new file mode 100644 index 00000000..96766763 --- /dev/null +++ b/frontend/src/pages/xray/OutboundFormModal.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/frontend/src/pages/xray/OutboundsTab.vue b/frontend/src/pages/xray/OutboundsTab.vue new file mode 100644 index 00000000..b488db30 --- /dev/null +++ b/frontend/src/pages/xray/OutboundsTab.vue @@ -0,0 +1,509 @@ + + + + + diff --git a/frontend/src/pages/xray/XrayPage.vue b/frontend/src/pages/xray/XrayPage.vue index e9888e8e..605b18da 100644 --- a/frontend/src/pages/xray/XrayPage.vue +++ b/frontend/src/pages/xray/XrayPage.vue @@ -17,6 +17,7 @@ import { message } from 'ant-design-vue'; import AppSidebar from '@/components/AppSidebar.vue'; import BasicsTab from './BasicsTab.vue'; import RoutingTab from './RoutingTab.vue'; +import OutboundsTab from './OutboundsTab.vue'; import { useXraySetting } from './useXraySetting.js'; // Phase 6-i: scaffold + advanced JSON tab. Other tabs (Basics, Routing, @@ -40,10 +41,20 @@ const { inboundTags, clientReverseTags, restartResult, + outboundsTraffic, + outboundTestStates, + fetchOutboundsTraffic, + resetOutboundsTraffic, + testOutbound, saveAll, restartXray, } = useXraySetting(); +async function onTestOutbound(idx) { + const outbound = templateSettings.value?.outbounds?.[idx]; + if (outbound) await testOutbound(idx, outbound); +} + // `WarpExist` / `NordExist` derive from the parsed templateSettings — // the Basics tab gates its WARP / NordVPN domain selectors on whether // the matching outbound is provisioned, falling back to a "configure" @@ -155,7 +166,17 @@ function confirmRestart() { - + diff --git a/frontend/src/pages/xray/useXraySetting.js b/frontend/src/pages/xray/useXraySetting.js index 65ecaf49..4e7bc74e 100644 --- a/frontend/src/pages/xray/useXraySetting.js +++ b/frontend/src/pages/xray/useXraySetting.js @@ -38,6 +38,13 @@ export function useXraySetting() { const clientReverseTags = ref([]); const restartResult = ref(''); + // Outbounds tab data — traffic stats + per-row test state. Test + // states are keyed by outbound index (sparse object), each entry + // is `{ testing, result }` where result is the wire response from + // /panel/xray/testOutbound or null while the test is in flight. + const outboundsTraffic = ref([]); + const outboundTestStates = ref({}); + async function fetchAll() { const msg = await HttpUtil.post('/panel/xray/'); if (!msg?.success) return; @@ -100,6 +107,42 @@ export function useXraySetting() { } } + async function fetchOutboundsTraffic() { + const msg = await HttpUtil.get('/panel/xray/getOutboundsTraffic'); + if (msg?.success) outboundsTraffic.value = msg.obj || []; + } + + async function resetOutboundsTraffic(tag) { + const msg = await HttpUtil.post('/panel/xray/resetOutboundsTraffic', { tag }); + if (msg?.success) await fetchOutboundsTraffic(); + } + + async function testOutbound(index, outbound) { + if (!outbound) return null; + if (!outboundTestStates.value[index]) outboundTestStates.value[index] = {}; + outboundTestStates.value[index] = { testing: true, result: null }; + try { + const msg = await HttpUtil.post('/panel/xray/testOutbound', { + outbound: JSON.stringify(outbound), + allOutbounds: JSON.stringify(templateSettings.value?.outbounds || []), + }); + if (msg?.success) { + outboundTestStates.value[index] = { testing: false, result: msg.obj }; + return msg.obj; + } + outboundTestStates.value[index] = { + testing: false, + result: { success: false, error: msg?.msg || 'Unknown error' }, + }; + } catch (e) { + outboundTestStates.value[index] = { + testing: false, + result: { success: false, error: String(e) }, + }; + } + return null; + } + async function restartXray() { spinning.value = true; try { @@ -136,6 +179,7 @@ export function useXraySetting() { onMounted(() => { fetchAll(); + fetchOutboundsTraffic(); startDirtyPoll(); }); onUnmounted(stopDirtyPoll); @@ -150,7 +194,12 @@ export function useXraySetting() { inboundTags, clientReverseTags, restartResult, + outboundsTraffic, + outboundTestStates, fetchAll, + fetchOutboundsTraffic, + resetOutboundsTraffic, + testOutbound, saveAll, restartXray, };