mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-02-27 20:53:01 +00:00
update
This commit is contained in:
parent
1a8b29aad3
commit
a6d9de10bc
12 changed files with 320 additions and 157 deletions
|
|
@ -54,7 +54,7 @@ func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
|||
}
|
||||
outboundTestUrl, _ := a.SettingService.GetXrayOutboundTestUrl()
|
||||
if outboundTestUrl == "" {
|
||||
outboundTestUrl = "http://www.google.com/gen_204"
|
||||
outboundTestUrl = "https://www.google.com/generate_204"
|
||||
}
|
||||
urlJSON, _ := json.Marshal(outboundTestUrl)
|
||||
xrayResponse := "{ \"xraySetting\": " + xraySetting + ", \"inboundTags\": " + inboundTags + ", \"outboundTestUrl\": " + string(urlJSON) + " }"
|
||||
|
|
@ -70,7 +70,7 @@ func (a *XraySettingController) updateSetting(c *gin.Context) {
|
|||
}
|
||||
outboundTestUrl := c.PostForm("outboundTestUrl")
|
||||
if outboundTestUrl == "" {
|
||||
outboundTestUrl = "http://www.google.com/gen_204"
|
||||
outboundTestUrl = "https://www.google.com/generate_204"
|
||||
}
|
||||
_ = a.SettingService.SetXrayOutboundTestUrl(outboundTestUrl)
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), nil)
|
||||
|
|
|
|||
|
|
@ -4,18 +4,22 @@
|
|||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||
<a-icon type="exclamation-circle" theme="filled"
|
||||
:style="{ color: '#FFA031' }"></a-icon>
|
||||
<span>{{ i18n "pages.xray.generalConfigsDesc" }}</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.FreedomStrategy" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.FreedomStrategyDesc" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.FreedomStrategyDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-select v-model="freedomStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
<a-select v-model="freedomStrategy"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
:style="{ width: '100%' }">
|
||||
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">
|
||||
<a-select-option v-for="s in OutboundDomainStrategies"
|
||||
:value="s">
|
||||
<span>[[ s ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
|
@ -23,11 +27,14 @@
|
|||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.RoutingStrategy" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.RoutingStrategyDesc" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.RoutingStrategyDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
<a-select v-model="routingStrategy"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
:style="{ width: '100%' }">
|
||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">
|
||||
<a-select-option v-for="s in routingDomainStrategies"
|
||||
:value="s">
|
||||
<span>[[ s ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
|
@ -35,37 +42,48 @@
|
|||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.outboundTestUrl" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.outboundTestUrlDesc" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.outboundTestUrlDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-input v-model="outboundTestUrl" :placeholder="'http://www.google.com/gen_204'" :style="{ width: '100%' }"></a-input>
|
||||
<a-input v-model="outboundTestUrl"
|
||||
:placeholder="'https://www.google.com/generate_204'"
|
||||
:style="{ width: '100%' }"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="2" header='{{ i18n "pages.xray.statistics" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.statsInboundUplink" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.statsInboundUplinkDesc" }}</template>
|
||||
<template #title>{{ i18n "pages.xray.statsInboundUplink"
|
||||
}}</template>
|
||||
<template #description>{{ i18n "pages.xray.statsInboundUplinkDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="statsInboundUplink"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.statsInboundDownlink" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.statsInboundDownlinkDesc" }}</template>
|
||||
<template #title>{{ i18n "pages.xray.statsInboundDownlink"
|
||||
}}</template>
|
||||
<template #description>{{ i18n "pages.xray.statsInboundDownlinkDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="statsInboundDownlink"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.statsOutboundUplink" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.statsOutboundUplinkDesc" }}</template>
|
||||
<template #title>{{ i18n "pages.xray.statsOutboundUplink"
|
||||
}}</template>
|
||||
<template #description>{{ i18n "pages.xray.statsOutboundUplinkDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="statsOutboundUplink"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.statsOutboundDownlink" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.statsOutboundDownlinkDesc" }}</template>
|
||||
<template #title>{{ i18n "pages.xray.statsOutboundDownlink"
|
||||
}}</template>
|
||||
<template #description>{{ i18n
|
||||
"pages.xray.statsOutboundDownlinkDesc" }}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="statsOutboundDownlink"></a-switch>
|
||||
</template>
|
||||
|
|
@ -75,16 +93,20 @@
|
|||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||
<a-icon type="exclamation-circle" theme="filled"
|
||||
:style="{ color: '#FFA031' }"></a-icon>
|
||||
<span>{{ i18n "pages.xray.logConfigsDesc" }}</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.logLevel" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.logLevelDesc" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.logLevelDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-select v-model="logLevel" :dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }">
|
||||
<a-select v-model="logLevel"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
:style="{ width: '100%' }">
|
||||
<a-select-option v-for="s in log.loglevel" :value="s">
|
||||
<span>[[ s ]]</span>
|
||||
</a-select-option>
|
||||
|
|
@ -93,10 +115,13 @@
|
|||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.accessLog" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.accessLogDesc" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.accessLogDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }">
|
||||
<a-select-option value=''>
|
||||
<a-select v-model="accessLog"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
:style="{ width: '100%' }">
|
||||
<a-select-option value>
|
||||
<span>Empty</span>
|
||||
</a-select-option>
|
||||
<a-select-option v-for="s in log.access" :value="s">
|
||||
|
|
@ -107,10 +132,13 @@
|
|||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.errorLog" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.errorLogDesc" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.errorLogDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }">
|
||||
<a-select-option value=''>
|
||||
<a-select v-model="errorLog"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
:style="{ width: '100%' }">
|
||||
<a-select-option value>
|
||||
<span>Empty</span>
|
||||
</a-select-option>
|
||||
<a-select-option v-for="s in log.error" :value="s">
|
||||
|
|
@ -121,11 +149,13 @@
|
|||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.maskAddress" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.maskAddressDesc" }}</template>
|
||||
<template #description>{{ i18n "pages.xray.maskAddressDesc"
|
||||
}}</template>
|
||||
<template #control>
|
||||
<a-select v-model="maskAddressLog" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
<a-select v-model="maskAddressLog"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
:style="{ width: '100%' }">
|
||||
<a-select-option value=''>
|
||||
<a-select-option value>
|
||||
<span>Empty</span>
|
||||
</a-select-option>
|
||||
<a-select-option v-for="s in log.maskAddress" :value="s">
|
||||
|
|
@ -146,7 +176,8 @@
|
|||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||
<a-icon type="exclamation-circle" theme="filled"
|
||||
:style="{ color: '#FFA031' }"></a-icon>
|
||||
<span>{{ i18n "pages.xray.blockConfigsDesc" }}</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
|
@ -160,17 +191,21 @@
|
|||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||
<span>{{ i18n "pages.xray.blockConnectionsConfigsDesc" }}</span>
|
||||
<a-icon type="exclamation-circle" theme="filled"
|
||||
:style="{ color: '#FFA031' }"></a-icon>
|
||||
<span>{{ i18n "pages.xray.blockConnectionsConfigsDesc"
|
||||
}}</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.blockips" }}</template>
|
||||
<template #control>
|
||||
<a-select mode="tags" v-model="blockedIPs" :style="{ width: '100%' }"
|
||||
<a-select mode="tags" v-model="blockedIPs"
|
||||
:style="{ width: '100%' }"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.IPsOptions">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.IPsOptions">
|
||||
<span>[[ p.label ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
|
@ -179,28 +214,35 @@
|
|||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.blockdomains" }}</template>
|
||||
<template #control>
|
||||
<a-select mode="tags" v-model="blockedDomains" :style="{ width: '100%' }"
|
||||
<a-select mode="tags" v-model="blockedDomains"
|
||||
:style="{ width: '100%' }"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.BlockDomainsOptions">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.BlockDomainsOptions">
|
||||
<span>[[ p.label ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||
<a-alert type="warning"
|
||||
:style="{ textAlign: 'center', marginTop: '20px' }">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||
<span>{{ i18n "pages.xray.directConnectionsConfigsDesc" }}</span>
|
||||
<a-icon type="exclamation-circle" theme="filled"
|
||||
:style="{ color: '#FFA031' }"></a-icon>
|
||||
<span>{{ i18n "pages.xray.directConnectionsConfigsDesc"
|
||||
}}</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.directips" }}</template>
|
||||
<template #control>
|
||||
<a-select mode="tags" :style="{ width: '100%' }" v-model="directIPs"
|
||||
<a-select mode="tags" :style="{ width: '100%' }"
|
||||
v-model="directIPs"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.IPsOptions">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.IPsOptions">
|
||||
<span>[[ p.label ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
|
@ -209,18 +251,22 @@
|
|||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.directdomains" }}</template>
|
||||
<template #control>
|
||||
<a-select mode="tags" :style="{ width: '100%' }" v-model="directDomains"
|
||||
<a-select mode="tags" :style="{ width: '100%' }"
|
||||
v-model="directDomains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.DomainsOptions">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.DomainsOptions">
|
||||
<span>[[ p.label ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||
<a-alert type="warning"
|
||||
:style="{ textAlign: 'center', marginTop: '20px' }">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||
<a-icon type="exclamation-circle" theme="filled"
|
||||
:style="{ color: '#FFA031' }"></a-icon>
|
||||
<span>{{ i18n "pages.xray.ipv4RoutingDesc" }}</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
|
@ -228,18 +274,22 @@
|
|||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.ipv4Routing" }}</template>
|
||||
<template #control>
|
||||
<a-select mode="tags" :style="{ width: '100%' }" v-model="ipv4Domains"
|
||||
<a-select mode="tags" :style="{ width: '100%' }"
|
||||
v-model="ipv4Domains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.ServicesOptions">
|
||||
<span>[[ p.label ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||
<a-alert type="warning"
|
||||
:style="{ textAlign: 'center', marginTop: '20px' }">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||
<a-icon type="exclamation-circle" theme="filled"
|
||||
:style="{ color: '#FFA031' }"></a-icon>
|
||||
{{ i18n "pages.xray.warpRoutingDesc" }}
|
||||
</template>
|
||||
</a-alert>
|
||||
|
|
@ -248,20 +298,24 @@
|
|||
<template #title>{{ i18n "pages.xray.warpRouting" }}</template>
|
||||
<template #control>
|
||||
<template v-if="WarpExist">
|
||||
<a-select mode="tags" :style="{ width: '100%' }" v-model="warpDomains"
|
||||
<a-select mode="tags" :style="{ width: '100%' }"
|
||||
v-model="warpDomains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.ServicesOptions">
|
||||
<span>[[ p.label ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-button type="primary" icon="cloud" @click="showWarp()">WARP</a-button>
|
||||
<a-button type="primary" icon="cloud"
|
||||
@click="showWarp()">WARP</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="6" header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||
<a-collapse-panel key="6"
|
||||
header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||
<a-space direction="horizontal" :style="{ padding: '0 20px' }">
|
||||
<a-button type="danger" @click="resetXrayConfigToDefault">
|
||||
<span>{{ i18n "pages.settings.resetDefaultConfig" }}</span>
|
||||
|
|
|
|||
|
|
@ -4,17 +4,22 @@
|
|||
<a-col :xs="12" :sm="12" :lg="12">
|
||||
<a-space direction="horizontal" size="small">
|
||||
<a-button type="primary" icon="plus" @click="addOutbound">
|
||||
<span v-if="!isMobile">{{ i18n "pages.xray.outbound.addOutbound" }}</span>
|
||||
<span v-if="!isMobile">{{ i18n
|
||||
"pages.xray.outbound.addOutbound" }}</span>
|
||||
</a-button>
|
||||
<a-button type="primary" icon="cloud" @click="showWarp()">WARP</a-button>
|
||||
<a-button type="primary" icon="cloud"
|
||||
@click="showWarp()">WARP</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :lg="12" :style="{ textAlign: 'right' }">
|
||||
<a-button-group>
|
||||
<a-button icon="sync" @click="refreshOutboundTraffic()" :loading="refreshing"></a-button>
|
||||
<a-popconfirm placement="topRight" @confirm="resetOutboundTraffic(-1)"
|
||||
<a-button icon="sync" @click="refreshOutboundTraffic()"
|
||||
:loading="refreshing"></a-button>
|
||||
<a-popconfirm placement="topRight"
|
||||
@confirm="resetOutboundTraffic(-1)"
|
||||
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
||||
:overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}'
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
ok-text='{{ i18n "reset"}}'
|
||||
cancel-text='{{ i18n "cancel"}}'>
|
||||
<a-icon slot="icon" type="question-circle-o"
|
||||
:style="{ color: themeSwitcher.isDarkTheme ? '#008771' : '#008771' }"></a-icon>
|
||||
|
|
@ -23,8 +28,10 @@
|
|||
</a-button-group>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-table :columns="outboundColumns" bordered :row-key="r => r.key" :data-source="outboundData"
|
||||
:scroll="isMobile ? {} : { x: 800 }" :pagination="false" :indent-size="0"
|
||||
<a-table :columns="outboundColumns" bordered :row-key="r => r.key"
|
||||
:data-source="outboundData"
|
||||
:scroll="isMobile ? {} : { x: 800 }" :pagination="false"
|
||||
:indent-size="0"
|
||||
:locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}` }'>
|
||||
<template slot="action" slot-scope="text, outbound, index">
|
||||
<span>[[ index+1 ]]</span>
|
||||
|
|
@ -32,7 +39,8 @@
|
|||
<a-icon @click="e => e.preventDefault()" type="more"
|
||||
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item v-if="index>0" @click="setFirstOutbound(index)">
|
||||
<a-menu-item v-if="index>0"
|
||||
@click="setFirstOutbound(index)">
|
||||
<a-icon type="vertical-align-top"></a-icon>
|
||||
<span>{{ i18n "pages.xray.rules.first"}}</span>
|
||||
</a-menu-item>
|
||||
|
|
@ -56,15 +64,21 @@
|
|||
</a-dropdown>
|
||||
</template>
|
||||
<template slot="address" slot-scope="text, outbound, index">
|
||||
<p :style="{ margin: '0 5px' }" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
|
||||
<p :style="{ margin: '0 5px' }"
|
||||
v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
|
||||
</template>
|
||||
<template slot="protocol" slot-scope="text, outbound, index">
|
||||
<a-tag :style="{ margin: '0' }" color="purple">[[ outbound.protocol ]]</a-tag>
|
||||
<a-tag :style="{ margin: '0' }" color="purple">[[ outbound.protocol
|
||||
]]</a-tag>
|
||||
<template
|
||||
v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
||||
<a-tag :style="{ margin: '0' }" color="blue">[[ outbound.streamSettings.network ]]</a-tag>
|
||||
<a-tag :style="{ margin: '0' }" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag>
|
||||
<a-tag :style="{ margin: '0' }" v-if="outbound.streamSettings.security=='reality'"
|
||||
<a-tag :style="{ margin: '0' }" color="blue">[[
|
||||
outbound.streamSettings.network ]]</a-tag>
|
||||
<a-tag :style="{ margin: '0' }"
|
||||
v-if="outbound.streamSettings.security=='tls'"
|
||||
color="green">tls</a-tag>
|
||||
<a-tag :style="{ margin: '0' }"
|
||||
v-if="outbound.streamSettings.security=='reality'"
|
||||
color="green">reality</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
|
|
@ -73,30 +87,37 @@
|
|||
</template>
|
||||
<template slot="test" slot-scope="text, outbound, index">
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.xray.outbound.test" }}</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
<template slot="title">{{ i18n "pages.xray.outbound.test"
|
||||
}}</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
icon="thunderbolt"
|
||||
icon="thunderbolt"
|
||||
:loading="outboundTestStates[index] && outboundTestStates[index].testing"
|
||||
@click="testOutbound(index)"
|
||||
:disabled="outboundTestStates[index] && outboundTestStates[index].testing">
|
||||
:disabled="(outbound.protocol === 'blackhole' || outbound.tag === 'blocked') || (outboundTestStates[index] && outboundTestStates[index].testing)">
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="testResult" slot-scope="text, outbound, index">
|
||||
<div v-if="outboundTestStates[index] && outboundTestStates[index].result">
|
||||
<a-tag v-if="outboundTestStates[index].result.success" color="green">
|
||||
<div
|
||||
v-if="outboundTestStates[index] && outboundTestStates[index].result">
|
||||
<a-tag v-if="outboundTestStates[index].result.success"
|
||||
color="green">
|
||||
[[ outboundTestStates[index].result.delay ]]ms
|
||||
<span v-if="outboundTestStates[index].result.statusCode"> (HTTP [[ outboundTestStates[index].result.statusCode ]])</span>
|
||||
<span v-if="outboundTestStates[index].result.statusCode">
|
||||
([[ outboundTestStates[index].result.statusCode
|
||||
]])</span>
|
||||
</a-tag>
|
||||
<a-tooltip v-else :title="outboundTestStates[index].result.error">
|
||||
<a-tooltip v-else
|
||||
:title="outboundTestStates[index].result.error">
|
||||
<a-tag color="red">
|
||||
Failed
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<span v-else-if="outboundTestStates[index] && outboundTestStates[index].testing">
|
||||
<span
|
||||
v-else-if="outboundTestStates[index] && outboundTestStates[index].testing">
|
||||
<a-icon type="loading" />
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
{{ template "page/head_start" .}}
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.min.css?{{ .cur_ver }}">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}">
|
||||
<link rel="stylesheet"
|
||||
href="{{ .base_path }}assets/codemirror/codemirror.min.css?{{ .cur_ver }}">
|
||||
<link rel="stylesheet"
|
||||
href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
||||
<link rel="stylesheet"
|
||||
href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
||||
{{ template "page/head_end" .}}
|
||||
|
||||
|
|
@ -10,10 +13,13 @@
|
|||
<a-sidebar></a-sidebar>
|
||||
<a-layout id="content-layout">
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||
<a-spin :spinning="loadingStates.spinning" :delay="500"
|
||||
tip='{{ i18n "loading"}}'>
|
||||
<transition name="list" appear>
|
||||
<a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }"
|
||||
message='{{ i18n "secAlertTitle" }}' color="red" description='{{ i18n "secAlertSsl" }}' show-icon closable>
|
||||
<a-alert type="error" v-if="showAlert && loadingStates.fetched"
|
||||
:style="{ marginBottom: '10px' }"
|
||||
message='{{ i18n "secAlertTitle" }}' color="red"
|
||||
description='{{ i18n "secAlertSsl" }}' show-icon closable>
|
||||
</a-alert>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
|
|
@ -26,19 +32,25 @@
|
|||
<a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-else>
|
||||
<a-col>
|
||||
<a-card hoverable>
|
||||
<a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }">
|
||||
<a-row
|
||||
:style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }">
|
||||
<a-col :xs="24" :sm="10" :style="{ padding: '4px' }">
|
||||
<a-space direction="horizontal">
|
||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">
|
||||
<a-button type="primary" :disabled="saveBtnDisable"
|
||||
@click="updateXraySetting">
|
||||
{{ i18n "pages.xray.save" }}
|
||||
</a-button>
|
||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">
|
||||
<a-button type="danger" :disabled="!saveBtnDisable"
|
||||
@click="restartXray">
|
||||
{{ i18n "pages.xray.restart" }}
|
||||
</a-button>
|
||||
<a-popover v-if="restartResult" :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<span slot="title">{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
|
||||
<a-popover v-if="restartResult"
|
||||
:overlay-class-name="themeSwitcher.currentTheme">
|
||||
<span slot="title">{{ i18n
|
||||
"pages.index.xrayErrorPopoverTitle" }}</span>
|
||||
<template slot="content">
|
||||
<span :style="{ maxWidth: '400px' }" v-for="line in restartResult.split('\n')">[[ line
|
||||
<span :style="{ maxWidth: '400px' }"
|
||||
v-for="line in restartResult.split('\n')">[[ line
|
||||
]]</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
|
|
@ -48,10 +60,13 @@
|
|||
<a-col :xs="24" :sm="14">
|
||||
<template>
|
||||
<div>
|
||||
<a-back-top :target="() => document.getElementById('content-layout')"
|
||||
<a-back-top
|
||||
:target="() => document.getElementById('content-layout')"
|
||||
visibility-height="200"></a-back-top>
|
||||
<a-alert type="warning" :style="{ float: 'right', width: 'fit-content' }"
|
||||
message='{{ i18n "pages.settings.infoDesc" }}' show-icon>
|
||||
<a-alert type="warning"
|
||||
:style="{ float: 'right', width: 'fit-content' }"
|
||||
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||
show-icon>
|
||||
</a-alert>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -60,7 +75,8 @@
|
|||
</a-card>
|
||||
</a-col>
|
||||
<a-col>
|
||||
<a-tabs default-active-key="tpl-basic" @change="(activeKey) => { this.changePage(activeKey); }"
|
||||
<a-tabs default-active-key="tpl-basic"
|
||||
@change="(activeKey) => { this.changePage(activeKey); }"
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<a-tab-pane key="tpl-basic" :style="{ paddingTop: '20px' }">
|
||||
<template #tab>
|
||||
|
|
@ -83,21 +99,24 @@
|
|||
</template>
|
||||
{{ template "settings/xray/outbounds" . }}
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tpl-reverse" :style="{ paddingTop: '20px' }" force-render="true">
|
||||
<a-tab-pane key="tpl-reverse" :style="{ paddingTop: '20px' }"
|
||||
force-render="true">
|
||||
<template #tab>
|
||||
<a-icon type="import"></a-icon>
|
||||
<span>{{ i18n "pages.xray.outbound.reverse"}}</span>
|
||||
</template>
|
||||
{{ template "settings/xray/reverse" . }}
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tpl-balancer" :style="{ paddingTop: '20px' }" force-render="true">
|
||||
<a-tab-pane key="tpl-balancer" :style="{ paddingTop: '20px' }"
|
||||
force-render="true">
|
||||
<template #tab>
|
||||
<a-icon type="cluster"></a-icon>
|
||||
<span>{{ i18n "pages.xray.Balancers"}}</span>
|
||||
</template>
|
||||
{{ template "settings/xray/balancers" . }}
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tpl-dns" :style="{ paddingTop: '20px' }" force-render="true">
|
||||
<a-tab-pane key="tpl-dns" :style="{ paddingTop: '20px' }"
|
||||
force-render="true">
|
||||
<template #tab>
|
||||
<a-icon type="database"></a-icon>
|
||||
<span>DNS</span>
|
||||
|
|
@ -120,14 +139,18 @@
|
|||
</a-layout>
|
||||
</a-layout>
|
||||
{{template "page/body_scripts" .}}
|
||||
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script>
|
||||
<script
|
||||
src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
||||
<script
|
||||
src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/lint/lint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/lint/javascript-lint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/hint/javascript-hint.js"></script>
|
||||
<script
|
||||
src="{{ .base_path }}assets/codemirror/lint/javascript-lint.js"></script>
|
||||
<script
|
||||
src="{{ .base_path }}assets/codemirror/hint/javascript-hint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/fold/foldcode.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/fold/foldgutter.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/fold/brace-fold.js"></script>
|
||||
|
|
@ -230,8 +253,8 @@
|
|||
},
|
||||
oldXraySetting: '',
|
||||
xraySetting: '',
|
||||
outboundTestUrl: 'http://www.google.com/gen_204',
|
||||
oldOutboundTestUrl: 'http://www.google.com/gen_204',
|
||||
outboundTestUrl: 'https://www.google.com/generate_204',
|
||||
oldOutboundTestUrl: 'https://www.google.com/generate_204',
|
||||
inboundTags: [],
|
||||
outboundsTraffic: [],
|
||||
outboundTestStates: {}, // Track testing state and results for each outbound
|
||||
|
|
@ -342,14 +365,14 @@
|
|||
},
|
||||
defaultObservatory: {
|
||||
subjectSelector: [],
|
||||
probeURL: "http://www.google.com/gen_204",
|
||||
probeURL: "https://www.google.com/generate_204",
|
||||
probeInterval: "10m",
|
||||
enableConcurrency: true
|
||||
},
|
||||
defaultBurstObservatory: {
|
||||
subjectSelector: [],
|
||||
pingConfig: {
|
||||
destination: "http://www.google.com/gen_204",
|
||||
destination: "https://www.google.com/generate_204",
|
||||
interval: "30m",
|
||||
connectivity: "http://connectivitycheck.platform.hicloud.com/generate_204",
|
||||
timeout: "10s",
|
||||
|
|
@ -380,7 +403,7 @@
|
|||
this.oldXraySetting = xs;
|
||||
this.xraySetting = xs;
|
||||
this.inboundTags = result.inboundTags;
|
||||
this.outboundTestUrl = result.outboundTestUrl || 'http://www.google.com/gen_204';
|
||||
this.outboundTestUrl = result.outboundTestUrl || 'https://www.google.com/generate_204';
|
||||
this.oldOutboundTestUrl = this.outboundTestUrl;
|
||||
this.saveBtnDisable = true;
|
||||
}
|
||||
|
|
@ -389,7 +412,7 @@
|
|||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/xray/update", {
|
||||
xraySetting: this.xraySetting,
|
||||
outboundTestUrl: this.outboundTestUrl || 'http://www.google.com/gen_204'
|
||||
outboundTestUrl: this.outboundTestUrl || 'https://www.google.com/generate_204'
|
||||
});
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
|
|
@ -612,6 +635,11 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (outbound.protocol === 'blackhole' || outbound.tag === 'blocked') {
|
||||
Vue.prototype.$message.warning('{{ i18n "pages.xray.outbound.testError" }}: blocked/blackhole outbound');
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize test state for this outbound if not exists
|
||||
if (!this.outboundTestStates[index]) {
|
||||
this.$set(this.outboundTestStates, index, {
|
||||
|
|
@ -626,7 +654,7 @@
|
|||
|
||||
try {
|
||||
const outboundJSON = JSON.stringify(outbound);
|
||||
const testURL = this.outboundTestUrl || 'http://www.google.com/gen_204';
|
||||
const testURL = this.outboundTestUrl || 'https://www.google.com/generate_204';
|
||||
const allOutboundsJSON = JSON.stringify(this.templateSettings.outbounds || []);
|
||||
|
||||
const msg = await HttpUtil.post("/panel/xray/testOutbound", {
|
||||
|
|
@ -644,7 +672,7 @@
|
|||
|
||||
if (result.success) {
|
||||
Vue.prototype.$message.success(
|
||||
`{{ i18n "pages.xray.outbound.testSuccess" }}: ${result.delay}ms (HTTP ${result.statusCode})`
|
||||
`{{ i18n "pages.xray.outbound.testSuccess" }}: ${result.delay}ms (${result.statusCode})`
|
||||
);
|
||||
} else {
|
||||
Vue.prototype.$message.error(
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func (j *XrayTrafficJob) Run() {
|
|||
}
|
||||
|
||||
// Broadcast traffic update via WebSocket with accumulated values from database
|
||||
trafficUpdate := map[string]interface{}{
|
||||
trafficUpdate := map[string]any{
|
||||
"traffics": traffics,
|
||||
"clientTraffics": clientTraffics,
|
||||
"onlineClients": onlineClients,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v2/config"
|
||||
|
|
@ -24,6 +27,9 @@ import (
|
|||
// It handles outbound traffic monitoring and statistics.
|
||||
type OutboundService struct{}
|
||||
|
||||
// testSemaphore limits concurrent outbound tests to prevent resource exhaustion.
|
||||
var testSemaphore sync.Mutex
|
||||
|
||||
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||
var err error
|
||||
db := database.GetDB()
|
||||
|
|
@ -125,11 +131,20 @@ type TestOutboundResult struct {
|
|||
// Only the test inbound and a route rule (to the tested outbound tag) are added.
|
||||
func (s *OutboundService) TestOutbound(outboundJSON string, testURL string, allOutboundsJSON string) (*TestOutboundResult, error) {
|
||||
if testURL == "" {
|
||||
testURL = "http://www.google.com/gen_204"
|
||||
testURL = "https://www.google.com/generate_204"
|
||||
}
|
||||
|
||||
// Limit to one concurrent test at a time
|
||||
if !testSemaphore.TryLock() {
|
||||
return &TestOutboundResult{
|
||||
Success: false,
|
||||
Error: "Another outbound test is already running, please wait",
|
||||
}, nil
|
||||
}
|
||||
defer testSemaphore.Unlock()
|
||||
|
||||
// Parse the outbound being tested to get its tag
|
||||
var testOutbound map[string]interface{}
|
||||
var testOutbound map[string]any
|
||||
if err := json.Unmarshal([]byte(outboundJSON), &testOutbound); err != nil {
|
||||
return &TestOutboundResult{
|
||||
Success: false,
|
||||
|
|
@ -143,9 +158,15 @@ func (s *OutboundService) TestOutbound(outboundJSON string, testURL string, allO
|
|||
Error: "Outbound has no tag",
|
||||
}, nil
|
||||
}
|
||||
if protocol, _ := testOutbound["protocol"].(string); protocol == "blackhole" || outboundTag == "blocked" {
|
||||
return &TestOutboundResult{
|
||||
Success: false,
|
||||
Error: "Blocked/blackhole outbound cannot be tested",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Use all outbounds when provided; otherwise fall back to single outbound
|
||||
var allOutbounds []interface{}
|
||||
var allOutbounds []any
|
||||
if allOutboundsJSON != "" {
|
||||
if err := json.Unmarshal([]byte(allOutboundsJSON), &allOutbounds); err != nil {
|
||||
return &TestOutboundResult{
|
||||
|
|
@ -155,7 +176,7 @@ func (s *OutboundService) TestOutbound(outboundJSON string, testURL string, allO
|
|||
}
|
||||
}
|
||||
if len(allOutbounds) == 0 {
|
||||
allOutbounds = []interface{}{testOutbound}
|
||||
allOutbounds = []any{testOutbound}
|
||||
}
|
||||
|
||||
// Find an available port for test inbound
|
||||
|
|
@ -185,8 +206,6 @@ func (s *OutboundService) TestOutbound(outboundJSON string, testURL string, allO
|
|||
defer func() {
|
||||
if testProcess.IsRunning() {
|
||||
testProcess.Stop()
|
||||
// Give it a moment to clean up
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
@ -198,8 +217,20 @@ func (s *OutboundService) TestOutbound(outboundJSON string, testURL string, allO
|
|||
}, nil
|
||||
}
|
||||
|
||||
// Wait a bit for xray to start
|
||||
time.Sleep(1 * time.Second)
|
||||
// Wait for xray to start listening on the test port
|
||||
if err := waitForPort(testPort, 3*time.Second); err != nil {
|
||||
if !testProcess.IsRunning() {
|
||||
result := testProcess.GetResult()
|
||||
return &TestOutboundResult{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("Xray process exited: %s", result),
|
||||
}, nil
|
||||
}
|
||||
return &TestOutboundResult{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("Xray failed to start listening: %v", err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Check if process is still running
|
||||
if !testProcess.IsRunning() {
|
||||
|
|
@ -228,7 +259,7 @@ func (s *OutboundService) TestOutbound(outboundJSON string, testURL string, allO
|
|||
|
||||
// createTestConfig creates a test config by copying all outbounds unchanged and adding
|
||||
// only the test inbound (SOCKS) and a route rule that sends traffic to the given outbound tag.
|
||||
func (s *OutboundService) createTestConfig(outboundTag string, allOutbounds []interface{}, testPort int) *xray.Config {
|
||||
func (s *OutboundService) createTestConfig(outboundTag string, allOutbounds []any, testPort int) *xray.Config {
|
||||
// Test inbound (SOCKS proxy) - only addition to inbounds
|
||||
testInbound := xray.InboundConfig{
|
||||
Tag: "test-inbound",
|
||||
|
|
@ -239,16 +270,20 @@ func (s *OutboundService) createTestConfig(outboundTag string, allOutbounds []in
|
|||
}
|
||||
|
||||
// Outbounds: copy all, but set noKernelTun=true for WireGuard outbounds
|
||||
processedOutbounds := make([]interface{}, len(allOutbounds))
|
||||
processedOutbounds := make([]any, len(allOutbounds))
|
||||
for i, ob := range allOutbounds {
|
||||
outbound := ob.(map[string]interface{})
|
||||
outbound, ok := ob.(map[string]any)
|
||||
if !ok {
|
||||
processedOutbounds[i] = ob
|
||||
continue
|
||||
}
|
||||
if protocol, ok := outbound["protocol"].(string); ok && protocol == "wireguard" {
|
||||
// Set noKernelTun to true for WireGuard outbounds
|
||||
if settings, ok := outbound["settings"].(map[string]interface{}); ok {
|
||||
if settings, ok := outbound["settings"].(map[string]any); ok {
|
||||
settings["noKernelTun"] = true
|
||||
} else {
|
||||
// Create settings if it doesn't exist
|
||||
outbound["settings"] = map[string]interface{}{
|
||||
outbound["settings"] = map[string]any{
|
||||
"noKernelTun": true,
|
||||
}
|
||||
}
|
||||
|
|
@ -258,7 +293,7 @@ func (s *OutboundService) createTestConfig(outboundTag string, allOutbounds []in
|
|||
outboundsJSON, _ := json.Marshal(processedOutbounds)
|
||||
|
||||
// Create routing rule to route all traffic through test outbound
|
||||
routingRules := []map[string]interface{}{
|
||||
routingRules := []map[string]any{
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": outboundTag,
|
||||
|
|
@ -266,19 +301,23 @@ func (s *OutboundService) createTestConfig(outboundTag string, allOutbounds []in
|
|||
},
|
||||
}
|
||||
|
||||
routingJSON, _ := json.Marshal(map[string]interface{}{
|
||||
routingJSON, _ := json.Marshal(map[string]any{
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": routingRules,
|
||||
})
|
||||
|
||||
// Disable logging for test process to avoid creating orphaned log files
|
||||
logConfig := map[string]any{
|
||||
"loglevel": "warning",
|
||||
"access": "none",
|
||||
"error": "none",
|
||||
"dnsLog": false,
|
||||
}
|
||||
logJSON, _ := json.Marshal(logConfig)
|
||||
|
||||
// Create minimal config
|
||||
config := &xray.Config{
|
||||
LogConfig: json_util.RawMessage(`{
|
||||
"loglevel":"info",
|
||||
"access":"` + config.GetBinFolderPath() + `/access_tests.log",
|
||||
"error":"` + config.GetBinFolderPath() + `/error_tests.log",
|
||||
"dnsLog":true
|
||||
}`),
|
||||
cfg := &xray.Config{
|
||||
LogConfig: json_util.RawMessage(logJSON),
|
||||
InboundConfigs: []xray.InboundConfig{
|
||||
testInbound,
|
||||
},
|
||||
|
|
@ -288,10 +327,12 @@ func (s *OutboundService) createTestConfig(outboundTag string, allOutbounds []in
|
|||
Stats: json_util.RawMessage(`{}`),
|
||||
}
|
||||
|
||||
return config
|
||||
return cfg
|
||||
}
|
||||
|
||||
// testConnection tests the connection through the proxy and measures delay
|
||||
// testConnection tests the connection through the proxy and measures delay.
|
||||
// It performs a warmup request first to establish the SOCKS connection and populate DNS caches,
|
||||
// then measures the second request for a more accurate latency reading.
|
||||
func (s *OutboundService) testConnection(proxyPort int, testURL string) (int64, int, error) {
|
||||
// Create SOCKS5 proxy URL
|
||||
proxyURL := fmt.Sprintf("socks5://127.0.0.1:%d", proxyPort)
|
||||
|
|
@ -302,18 +343,32 @@ func (s *OutboundService) testConnection(proxyPort int, testURL string) (int64,
|
|||
return 0, 0, common.NewErrorf("Invalid proxy URL: %v", err)
|
||||
}
|
||||
|
||||
// Create HTTP client with proxy
|
||||
// Create HTTP client with proxy and keep-alive for connection reuse
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURLParsed),
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 10 * time.Second,
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
MaxIdleConns: 1,
|
||||
IdleConnTimeout: 10 * time.Second,
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Measure time
|
||||
// Warmup request: establishes SOCKS/TLS connection, DNS, and TCP to the target.
|
||||
// This mirrors real-world usage where connections are reused.
|
||||
warmupResp, err := client.Get(testURL)
|
||||
if err != nil {
|
||||
return 0, 0, common.NewErrorf("Request failed: %v", err)
|
||||
}
|
||||
io.Copy(io.Discard, warmupResp.Body)
|
||||
warmupResp.Body.Close()
|
||||
|
||||
// Measure the actual request on the warm connection
|
||||
startTime := time.Now()
|
||||
resp, err := client.Get(testURL)
|
||||
delay := time.Since(startTime).Milliseconds()
|
||||
|
|
@ -321,11 +376,26 @@ func (s *OutboundService) testConnection(proxyPort int, testURL string) (int64,
|
|||
if err != nil {
|
||||
return 0, 0, common.NewErrorf("Request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
return delay, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
// waitForPort polls until the given TCP port is accepting connections or the timeout expires.
|
||||
func waitForPort(port int, timeout time.Duration) error {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 100*time.Millisecond)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("port %d not ready after %v", port, timeout)
|
||||
}
|
||||
|
||||
// findAvailablePort finds an available port for testing
|
||||
func findAvailablePort() (int, error) {
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
|
|
@ -339,7 +409,7 @@ func findAvailablePort() (int, error) {
|
|||
}
|
||||
|
||||
// createTestConfigPath returns a unique path for a temporary xray config file in the bin folder.
|
||||
// The file is not created; the path is reserved by creating and then removing an empty temp file.
|
||||
// The temp file is created and closed so the path is reserved; Start() will overwrite it.
|
||||
func createTestConfigPath() (string, error) {
|
||||
tmpFile, err := os.CreateTemp(config.GetBinFolderPath(), "xray_test_*.json")
|
||||
if err != nil {
|
||||
|
|
@ -350,8 +420,5 @@ func createTestConfigPath() (string, error) {
|
|||
os.Remove(path)
|
||||
return "", err
|
||||
}
|
||||
if err := os.Remove(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ var defaultValueMap = map[string]string{
|
|||
"warp": "",
|
||||
"externalTrafficInformEnable": "false",
|
||||
"externalTrafficInformURI": "",
|
||||
"xrayOutboundTestUrl": "https://www.google.com/generate_204",
|
||||
|
||||
// LDAP defaults
|
||||
"ldapEnable": "false",
|
||||
"ldapHost": "",
|
||||
|
|
@ -99,7 +101,6 @@ var defaultValueMap = map[string]string{
|
|||
"ldapDefaultTotalGB": "0",
|
||||
"ldapDefaultExpiryDays": "0",
|
||||
"ldapDefaultLimitIP": "0",
|
||||
"xrayOutboundTestUrl": "http://www.google.com/gen_204",
|
||||
}
|
||||
|
||||
// SettingService provides business logic for application settings management.
|
||||
|
|
|
|||
|
|
@ -427,8 +427,6 @@
|
|||
"information" = "Information"
|
||||
"language" = "Language"
|
||||
"telegramBotLanguage" = "Telegram Bot Language"
|
||||
"outboundTestURL" = "Outbound Test URL"
|
||||
"outboundTestURLDesc" = "URL used to test outbound connection"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray Configs"
|
||||
|
|
@ -463,7 +461,7 @@
|
|||
"RoutingStrategy" = "Overall Routing Strategy"
|
||||
"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests."
|
||||
"outboundTestUrl" = "Outbound Test URL"
|
||||
"outboundTestUrlDesc" = "URL used when testing outbound connectivity. Default is http://www.google.com/gen_204"
|
||||
"outboundTestUrlDesc" = "URL used when testing outbound connectivity."
|
||||
"Torrent" = "Block BitTorrent Protocol"
|
||||
"Inbounds" = "Inbounds"
|
||||
"InboundsDesc" = "Accepting the specific clients."
|
||||
|
|
|
|||
|
|
@ -427,8 +427,6 @@
|
|||
"information" = "اطلاعات"
|
||||
"language" = "زبان"
|
||||
"telegramBotLanguage" = "زبان ربات تلگرام"
|
||||
"outboundTestURL" = "آدرس تست خروجی"
|
||||
"outboundTestURLDesc" = "آدرسی که برای تست اتصال خروجی استفاده میشود"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "پیکربندی ایکسری"
|
||||
|
|
@ -463,7 +461,7 @@
|
|||
"RoutingStrategy" = "استراتژی کلی مسیریابی"
|
||||
"RoutingStrategyDesc" = "استراتژی کلی مسیریابی برای حل تمام درخواستها را تعیین میکند"
|
||||
"outboundTestUrl" = "آدرس تست خروجی"
|
||||
"outboundTestUrlDesc" = "آدرسی که برای تست اتصال خروجی استفاده میشود. پیشفرض: http://www.google.com/gen_204"
|
||||
"outboundTestUrlDesc" = "آدرسی که برای تست اتصال خروجی استفاده میشود."
|
||||
"Torrent" = "مسدودسازی پروتکل بیتتورنت"
|
||||
"Inbounds" = "ورودیها"
|
||||
"InboundsDesc" = "پذیرش کلاینت خاص"
|
||||
|
|
|
|||
|
|
@ -427,8 +427,6 @@
|
|||
"information" = "Информация"
|
||||
"language" = "Язык интерфейса"
|
||||
"telegramBotLanguage" = "Язык Telegram-бота"
|
||||
"outboundTestURL" = "URL для тестирования исходящего подключения"
|
||||
"outboundTestURLDesc" = "URL, используемый для тестирования исходящего подключения"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Настройки Xray"
|
||||
|
|
@ -463,7 +461,7 @@
|
|||
"RoutingStrategy" = "Настройка маршрутизации доменов"
|
||||
"RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS"
|
||||
"outboundTestUrl" = "URL для теста исходящего"
|
||||
"outboundTestUrlDesc" = "URL для проверки подключения исходящего. По умолчанию: http://www.google.com/gen_204"
|
||||
"outboundTestUrlDesc" = "URL для проверки подключения исходящего"
|
||||
"Torrent" = "Заблокировать BitTorrent"
|
||||
"Inbounds" = "Входящие подключения"
|
||||
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных клиентов"
|
||||
|
|
|
|||
|
|
@ -427,8 +427,6 @@
|
|||
"information" = "信息"
|
||||
"language" = "语言"
|
||||
"telegramBotLanguage" = "Telegram 机器人语言"
|
||||
"outboundTestURL" = "出站测试 URL"
|
||||
"outboundTestURLDesc" = "用于测试出站连接的 URL"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray 配置"
|
||||
|
|
@ -463,7 +461,7 @@
|
|||
"RoutingStrategy" = "配置路由域策略"
|
||||
"RoutingStrategyDesc" = "设置 DNS 解析的整体路由策略"
|
||||
"outboundTestUrl" = "出站测试 URL"
|
||||
"outboundTestUrlDesc" = "测试出站连接时使用的 URL,默认为 http://www.google.com/gen_204"
|
||||
"outboundTestUrlDesc" = "测试出站连接时使用的 URL"
|
||||
"Torrent" = "屏蔽 BitTorrent 协议"
|
||||
"Inbounds" = "入站规则"
|
||||
"InboundsDesc" = "接受来自特定客户端的流量"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func BroadcastInbounds(inbounds any) {
|
|||
}
|
||||
|
||||
// BroadcastOutbounds broadcasts outbounds list update to all connected clients
|
||||
func BroadcastOutbounds(outbounds interface{}) {
|
||||
func BroadcastOutbounds(outbounds any) {
|
||||
hub := GetHub()
|
||||
if hub != nil {
|
||||
hub.Broadcast(MessageTypeOutbounds, outbounds)
|
||||
|
|
|
|||
Loading…
Reference in a new issue