xray.html 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. {{template "head" .}}
  4. <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
  5. <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
  6. <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css">
  7. <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
  8. <script src="{{ .base_path }}assets/js/model/outbound.js"></script>
  9. <script src="{{ .base_path }}assets/codemirror/codemirror.js"></script>
  10. <script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
  11. <script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
  12. <script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
  13. <script src="{{ .base_path }}assets/codemirror/lint/lint.js"></script>
  14. <script src="{{ .base_path }}assets/codemirror/lint/javascript-lint.js"></script>
  15. <script src="{{ .base_path }}assets/codemirror/hint/javascript-hint.js"></script>
  16. <script src="{{ .base_path }}assets/codemirror/fold/foldcode.js"></script>
  17. <script src="{{ .base_path }}assets/codemirror/fold/foldgutter.js"></script>
  18. <script src="{{ .base_path }}assets/codemirror/fold/brace-fold.js"></script>
  19. <style>
  20. @media (min-width: 769px) {
  21. .ant-layout-content {
  22. margin: 24px 16px;
  23. }
  24. }
  25. @media (max-width: 768px) {
  26. .ant-tabs-nav .ant-tabs-tab {
  27. margin: 0;
  28. padding: 12px .5rem;
  29. }
  30. }
  31. .ant-tabs-bar {
  32. margin: 0;
  33. }
  34. .ant-list-item {
  35. display: block;
  36. }
  37. .collapse-title {
  38. color: inherit;
  39. font-weight: bold;
  40. font-size: 18px;
  41. padding: 10px 20px;
  42. border-bottom: 2px solid;
  43. }
  44. .collapse-title > i {
  45. color: inherit;
  46. font-size: 24px;
  47. }
  48. </style>
  49. <body>
  50. <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
  51. {{ template "commonSider" . }}
  52. <a-layout id="content-layout">
  53. <a-layout-content>
  54. <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
  55. <a-space direction="vertical">
  56. <a-card hoverable style="margin-bottom: .5rem;">
  57. <a-row>
  58. <a-col :xs="24" :sm="8" style="padding: 4px;">
  59. <a-space direction="horizontal">
  60. <a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
  61. <a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
  62. </a-space>
  63. </a-col>
  64. <a-col :xs="24" :sm="16">
  65. <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
  66. </a-back-top>
  67. <a-alert type="warning" style="float: right; width: fit-content"
  68. message='{{ i18n "pages.settings.infoDesc" }}'
  69. show-icon
  70. >
  71. </a-col>
  72. </a-row>
  73. </a-card>
  74. <a-tabs class="ant-card-dark-box-nohover" default-active-key="tpl-1"
  75. @change="(activeKey) => { if(activeKey == 'tpl-4') this.changeCode(); }"
  76. :class="themeSwitcher.currentTheme">
  77. <a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}'>
  78. <a-space direction="horizontal" style="padding: 20px 20px">
  79. <a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
  80. </a-space>
  81. <a-collapse>
  82. <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
  83. <a-row :xs="24" :sm="24" :lg="12">
  84. <a-alert type="warning" style="text-align: center;">
  85. <template slot="message">
  86. <a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
  87. {{ i18n "pages.xray.generalConfigsDesc" }}
  88. </template>
  89. </a-alert>
  90. </a-row>
  91. <a-list-item>
  92. <a-row style="padding: 20px">
  93. <a-col :lg="24" :xl="12">
  94. <a-list-item-meta
  95. title='{{ i18n "pages.xray.xrayConfigFreedomStrategy" }}'
  96. description='{{ i18n "pages.xray.xrayConfigFreedomStrategyDesc" }}'/>
  97. </a-col>
  98. <a-col :lg="24" :xl="12">
  99. <template>
  100. <a-select
  101. v-model="freedomStrategy"
  102. :dropdown-class-name="themeSwitcher.currentTheme"
  103. style="width: 100%">
  104. <a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
  105. </a-select>
  106. </template>
  107. </a-col>
  108. </a-row>
  109. </a-list-item>
  110. <a-row style="padding: 20px">
  111. <a-col :lg="24" :xl="12">
  112. <a-list-item-meta
  113. title='{{ i18n "pages.xray.xrayConfigRoutingStrategy" }}'
  114. description='{{ i18n "pages.xray.xrayConfigRoutingStrategyDesc" }}'/>
  115. </a-col>
  116. <a-col :lg="24" :xl="12">
  117. <template>
  118. <a-select
  119. v-model="routingStrategy"
  120. :dropdown-class-name="themeSwitcher.currentTheme"
  121. style="width: 100%">
  122. <a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
  123. </a-select>
  124. </template>
  125. </a-col>
  126. </a-row>
  127. </a-list-item>
  128. </a-collapse-panel>
  129. <a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
  130. <a-row :xs="24" :sm="24" :lg="12">
  131. <a-alert type="warning" style="text-align: center;">
  132. <template slot="message">
  133. <a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
  134. {{ i18n "pages.xray.blockConfigsDesc" }}
  135. </template>
  136. </a-alert>
  137. </a-row>
  138. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigTorrent"}}' desc='{{ i18n "pages.xray.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
  139. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.xray.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
  140. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigAds"}}' desc='{{ i18n "pages.xray.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
  141. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigFamily"}}' desc='{{ i18n "pages.xray.xrayConfigFamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
  142. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigSpeedtest"}}' desc='{{ i18n "pages.xray.xrayConfigSpeedtestDesc"}}' v-model="SpeedTestSettings"></setting-list-item>
  143. </a-collapse-panel>
  144. <a-collapse-panel header='{{ i18n "pages.xray.blockCountryConfigs"}}'>
  145. <a-row :xs="24" :sm="24" :lg="12">
  146. <a-alert type="warning" style="text-align: center;">
  147. <template slot="message">
  148. <a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
  149. {{ i18n "pages.xray.blockCountryConfigsDesc" }}
  150. </template>
  151. </a-alert>
  152. </a-row>
  153. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigIRIp"}}' desc='{{ i18n "pages.xray.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
  154. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigIRDomain"}}' desc='{{ i18n "pages.xray.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
  155. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigChinaIp"}}' desc='{{ i18n "pages.xray.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
  156. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.xray.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
  157. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.xray.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
  158. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.xray.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
  159. </a-collapse-panel>
  160. <a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}'>
  161. <a-row :xs="24" :sm="24" :lg="12">
  162. <a-alert type="warning" style="text-align: center;">
  163. <template slot="message">
  164. <a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
  165. {{ i18n "pages.xray.directCountryConfigsDesc" }}
  166. </template>
  167. </a-alert>
  168. </a-row>
  169. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectIRIp"}}' desc='{{ i18n "pages.xray.xrayConfigDirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
  170. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectIRDomain"}}' desc='{{ i18n "pages.xray.xrayConfigDirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
  171. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectChinaIp"}}' desc='{{ i18n "pages.xray.xrayConfigDirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
  172. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectChinaDomain"}}' desc='{{ i18n "pages.xray.xrayConfigDirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
  173. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectRussiaIp"}}' desc='{{ i18n "pages.xray.xrayConfigDirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
  174. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectRussiaDomain"}}' desc='{{ i18n "pages.xray.xrayConfigDirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
  175. </a-collapse-panel>
  176. <a-collapse-panel header='{{ i18n "pages.xray.ipv4Configs"}}'>
  177. <a-row :xs="24" :sm="24" :lg="12">
  178. <a-alert type="warning" style="text-align: center;">
  179. <template slot="message">
  180. <a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
  181. {{ i18n "pages.xray.ipv4ConfigsDesc" }}
  182. </template>
  183. </a-alert>
  184. </a-row>
  185. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.xray.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
  186. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.xray.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
  187. </a-collapse-panel>
  188. <a-collapse-panel header='{{ i18n "pages.xray.warpConfigs"}}'>
  189. <a-row :xs="24" :sm="24" :lg="12">
  190. <a-alert type="warning" style="text-align: center;">
  191. <template slot="message">
  192. <a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
  193. {{ i18n "pages.xray.warpConfigsDesc" }}
  194. </template>
  195. </a-alert>
  196. </a-row>
  197. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.xray.xrayConfigGoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
  198. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.xray.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
  199. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.xray.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
  200. <setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.xray.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
  201. </a-collapse-panel>
  202. </a-collapse>
  203. </a-tab-pane>
  204. <a-tab-pane key="tpl-2" tab='{{ i18n "pages.xray.Routings"}}' style="padding-top: 20px;">
  205. <a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
  206. message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
  207. <a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
  208. <a-table :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
  209. :row-key="r => r.key"
  210. :data-source="routingRuleData"
  211. :scroll="isMobile ? {} : { x: 1000 }"
  212. :pagination="false"
  213. :indent-size="0"
  214. :style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'">
  215. <template slot="action" slot-scope="text, rule, index">
  216. [[ index+1 ]]
  217. <a-dropdown :trigger="['click']">
  218. <a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
  219. <a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
  220. <a-menu-item v-if="index>0" @click="replaceRule(index,0)">
  221. <a-icon type="vertical-align-top"></a-icon>
  222. {{ i18n "pages.xray.rules.first"}}
  223. </a-menu-item>
  224. <a-menu-item v-if="index>0" @click="replaceRule(index,index-1)">
  225. <a-icon type="arrow-up"></a-icon>
  226. {{ i18n "pages.xray.rules.up"}}
  227. </a-menu-item>
  228. <a-menu-item v-if="index<routingRuleData.length-1" @click="replaceRule(index,index+1)">
  229. <a-icon type="arrow-down"></a-icon>
  230. {{ i18n "pages.xray.rules.down"}}
  231. </a-menu-item>
  232. <a-menu-item v-if="index<routingRuleData.length-1" @click="replaceRule(index,routingRuleData.length-1)">
  233. <a-icon type="vertical-align-bottom"></a-icon>
  234. {{ i18n "pages.xray.rules.last"}}
  235. </a-menu-item>
  236. <a-menu-item @click="editRule(index)">
  237. <a-icon type="edit"></a-icon>
  238. {{ i18n "edit" }}
  239. </a-menu-item>
  240. <a-menu-item @click="deleteRule(index)">
  241. <span style="color: #FF4D4F">
  242. <a-icon type="delete"></a-icon> {{ i18n "delete"}}
  243. </span>
  244. </a-menu-item>
  245. </a-menu>
  246. </a-dropdown>
  247. </template>
  248. <template slot="inbound" slot-scope="text, rule, index">
  249. <a-popover :overlay-class-name="themeSwitcher.currentTheme">
  250. <template slot="content">
  251. <p v-if="rule.inboundTag">Inbound Tag: [[ rule.inboundTag ]]</p>
  252. <p v-if="rule.user">User email: [[ rule.user ]]</p>
  253. </template>
  254. [[ [rule.inboundTag,rule.user].join('\n') ]]
  255. </a-popover>
  256. </template>
  257. <template slot="outbound" slot-scope="text, rule, index">
  258. <a-popover :overlay-class-name="themeSwitcher.currentTheme">
  259. <template slot="content">
  260. <p v-if="rule.outboundTag">Outbound Tag: [[ rule.outboundTag ]]</p>
  261. </template>
  262. [[ rule.outboundTag ]]
  263. </a-popover>
  264. </template>
  265. <template slot="info" slot-scope="text, rule, index">
  266. <a-popover placement="bottomRight"
  267. v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
  268. :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
  269. <template slot="content">
  270. <table cellpadding="2" style="max-width: 300px;">
  271. <tr v-if="rule.source">
  272. <td>Source</td>
  273. <td><a-tag color="blue" v-for="r in rule.source.split(',')">[[ r ]]</a-tag></td>
  274. </tr>
  275. <tr v-if="rule.sourcePort">
  276. <td>Source Port</td>
  277. <td><a-tag color="green" v-for="r in rule.sourcePort.split(',')">[[ r ]]</a-tag></td>
  278. </tr>
  279. <tr v-if="rule.network">
  280. <td>Network</td>
  281. <td><a-tag color="blue" v-for="r in rule.network.split(',')">[[ r ]]</a-tag></td>
  282. </tr>
  283. <tr v-if="rule.protocol">
  284. <td>Protocol</td>
  285. <td><a-tag color="green" v-for="r in rule.protocol.split(',')">[[ r ]]</a-tag></td>
  286. </tr>
  287. <tr v-if="rule.attrs">
  288. <td>Attrs</td>
  289. <td><a-tag color="blue" v-for="r in rule.attrs.split(',')">[[ r ]]</a-tag></td>
  290. </tr>
  291. <tr v-if="rule.ip">
  292. <td>IP</td>
  293. <td><a-tag color="green" v-for="r in rule.ip.split(',')">[[ r ]]</a-tag></td>
  294. </tr>
  295. <tr v-if="rule.domain">
  296. <td>Domain</td>
  297. <td><a-tag color="blue" v-for="r in rule.domain.split(',')">[[ r ]]</a-tag></td>
  298. </tr>
  299. <tr v-if="rule.port">
  300. <td>Port</td>
  301. <td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
  302. </tr>
  303. </table>
  304. </template>
  305. <a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
  306. <a-icon type="info"></a-icon>
  307. </a-button>
  308. </a-popover>
  309. </template>
  310. </a-table>
  311. </a-tab-pane>
  312. <a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
  313. <a-button type="primary" icon="plus" @click="addOutbound()">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
  314. <a-button type="primary" icon="plus" @click="addReverse()">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
  315. <a-row>
  316. <a-col :sm="24" :md="12">
  317. <p style="margin: 10px;">{{ i18n "pages.xray.Outbounds"}}</p>
  318. <a-table :columns="outboundColumns" bordered
  319. :row-key="r => r.key"
  320. :data-source="outboundData"
  321. :scroll="isMobile ? {} : { x: 200 }"
  322. :pagination="false"
  323. :indent-size="0"
  324. :style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'">
  325. <template slot="action" slot-scope="text, outbound, index">
  326. [[ index+1 ]]
  327. <a-dropdown :trigger="['click']">
  328. <a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
  329. <a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
  330. <a-menu-item @click="editOutbound(index)">
  331. <a-icon type="edit"></a-icon>
  332. {{ i18n "edit" }}
  333. </a-menu-item>
  334. <a-menu-item @click="deleteOutbound(index)">
  335. <span style="color: #FF4D4F">
  336. <a-icon type="delete"></a-icon> {{ i18n "delete"}}
  337. </span>
  338. </a-menu-item>
  339. </a-menu>
  340. </a-dropdown>
  341. </template>
  342. <template slot="address" slot-scope="text, outbound, index">
  343. <p style="margin: 0 5px;" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
  344. </template>
  345. <template slot="protocol" slot-scope="text, outbound, index">
  346. <a-tag style="margin:0;" color="purple">[[ outbound.protocol ]]</a-tag>
  347. <template v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
  348. <a-tag style="margin:0;" color="blue">[[ outbound.streamSettings.network ]]</a-tag>
  349. <a-tag style="margin:0;" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag>
  350. <a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
  351. </template>
  352. </template>
  353. </a-table>
  354. </a-col>
  355. <a-col :sm="24" :md="12" v-if="reverseData.length>0">
  356. <p style="margin: 10px;">{{ i18n "pages.xray.outbound.reverse"}}</p>
  357. <a-table :columns="reverseColumns" bordered
  358. :row-key="r => r.key"
  359. :data-source="reverseData"
  360. :scroll="isMobile ? {} : { x: 200 }"
  361. :pagination="false"
  362. :indent-size="0"
  363. :style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
  364. <template slot="action" slot-scope="text, reverse, index">
  365. [[ index+1 ]]
  366. <a-dropdown :trigger="['click']">
  367. <a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
  368. <a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
  369. <a-menu-item @click="editReverse(index)">
  370. <a-icon type="edit"></a-icon>
  371. {{ i18n "edit" }}
  372. </a-menu-item>
  373. <a-menu-item @click="deleteReverse(index)">
  374. <span style="color: #FF4D4F">
  375. <a-icon type="delete"></a-icon> {{ i18n "delete"}}
  376. </span>
  377. </a-menu-item>
  378. </a-menu>
  379. </a-dropdown>
  380. </template>
  381. </a-table>
  382. </a-col>
  383. </a-row>
  384. </a-tab-pane>
  385. <a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
  386. <a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
  387. <a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
  388. <a-radio-button value="xraySetting">{{ i18n "pages.xray.completeTemplate"}}</a-radio-button>
  389. <a-radio-button value="inboundSettings">{{ i18n "pages.xray.Inbounds" }}</a-radio-button>
  390. <a-radio-button value="outboundSettings">{{ i18n "pages.xray.Outbounds" }}</a-radio-button>
  391. <a-radio-button value="routingRuleSettings">{{ i18n "pages.xray.Routings" }}</a-radio-button>
  392. </a-radio-group>
  393. <textarea style="position:absolute; left: -800px;" id="xraySetting"></textarea>
  394. </a-tab-pane>
  395. </a-tabs>
  396. </a-space>
  397. </a-spin>
  398. </a-layout-content>
  399. </a-layout>
  400. </a-layout>
  401. {{template "js" .}}
  402. {{template "component/themeSwitcher" .}}
  403. {{template "component/setting"}}
  404. {{template "ruleModal"}}
  405. {{template "outModal"}}
  406. {{template "reverseModal"}}
  407. <script>
  408. const rulesColumns = [
  409. { title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } },
  410. { title: '{{ i18n "pages.xray.rules.source"}}', children: [
  411. { title: 'IP', dataIndex: "source", align: 'center', width: 20, ellipsis: true },
  412. { title: 'port', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true } ]},
  413. { title: '{{ i18n "pages.inbounds.network"}}', children: [
  414. { title: 'L4', dataIndex: 'network', align: 'center', width: 10 },
  415. { title: 'Protocol', dataIndex: 'protocol', align: 'center', width: 10, ellipsis: true },
  416. { title: 'Attrs', dataIndex: 'attrs', align: 'center', width: 20, ellipsis: true } ]},
  417. { title: '{{ i18n "pages.xray.rules.dest"}}', children: [
  418. { title: 'IP', dataIndex: 'ip', align: 'center', width: 20, ellipsis: true },
  419. { title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
  420. { title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
  421. { title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
  422. { title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true },
  423. { title: 'User email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
  424. { title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 },
  425. ];
  426. const rulesMobileColumns = [
  427. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  428. { title: '{{ i18n "pages.xray.rules.inbound"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'inbound' } },
  429. { title: '{{ i18n "pages.xray.rules.outbound"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'outbound' } },
  430. { title: '{{ i18n "pages.xray.rules.info"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'info' } },
  431. ];
  432. const outboundColumns = [
  433. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  434. { title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
  435. { title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
  436. { title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
  437. ];
  438. const reverseColumns = [
  439. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  440. { title: '{{ i18n "pages.xray.outbound.type"}}', dataIndex: 'type', align: 'center', width: 50 },
  441. { title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
  442. { title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
  443. ];
  444. const app = new Vue({
  445. delimiters: ['[[', ']]'],
  446. el: '#app',
  447. data: {
  448. siderDrawer,
  449. themeSwitcher,
  450. isDarkTheme: themeSwitcher.isDarkTheme,
  451. spinning: false,
  452. oldXraySetting: '',
  453. xraySetting: '',
  454. inboundTags: [],
  455. saveBtnDisable: true,
  456. isMobile: window.innerWidth <= 768,
  457. advSettings: 'xraySetting',
  458. cm: null,
  459. cmOptions: {
  460. lineNumbers: true,
  461. mode: "application/json",
  462. lint: true,
  463. styleActiveLine: true,
  464. matchBrackets: true,
  465. theme: "xq",
  466. autoCloseTags: true,
  467. lineWrapping: true,
  468. indentUnit: 2,
  469. indentWithTabs: true,
  470. smartIndent: true,
  471. tabSize: 2,
  472. lineWiseCopyCut: false,
  473. foldGutter: true,
  474. gutters: [
  475. "CodeMirror-lint-markers",
  476. "CodeMirror-linenumbers",
  477. "CodeMirror-foldgutter",
  478. ],
  479. },
  480. ipv4Settings: {
  481. tag: "IPv4",
  482. protocol: "freedom",
  483. settings: {
  484. domainStrategy: "UseIPv4"
  485. }
  486. },
  487. warpSettings: {
  488. tag: "WARP",
  489. protocol: "socks",
  490. settings: {
  491. servers: [
  492. {
  493. address: "127.0.0.1",
  494. port: 40000
  495. }
  496. ]
  497. }
  498. },
  499. directSettings: {
  500. tag: "direct",
  501. protocol: "freedom"
  502. },
  503. outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
  504. routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
  505. settingsData: {
  506. protocols: {
  507. bittorrent: ["bittorrent"],
  508. },
  509. ips: {
  510. local: ["geoip:private"],
  511. cn: ["geoip:cn"],
  512. ir: ["ext:geoip_IR.dat:ir","ext:geoip_IR.dat:arvancloud","ext:geoip_IR.dat:derakcloud","ext:geoip_IR.dat:iranserver"],
  513. ru: ["geoip:ru"],
  514. },
  515. domains: {
  516. ads: [
  517. "geosite:category-ads-all",
  518. "ext:geosite_IR.dat:category-ads-all"
  519. ],
  520. speedtest: ["geosite:speedtest"],
  521. openai: ["geosite:openai"],
  522. google: ["geosite:google"],
  523. spotify: ["geosite:spotify"],
  524. netflix: ["geosite:netflix"],
  525. cn: [
  526. "geosite:cn",
  527. "regexp:.*\\.cn$"
  528. ],
  529. ru: [
  530. "geosite:category-gov-ru",
  531. "regexp:.*\\.ru$"
  532. ],
  533. ir: [
  534. "regexp:.*\\.ir$",
  535. "regexp:.*\\.xn--mgba3a4f16a$", // .ایران
  536. "ext:geosite_IR.dat:ir" // have rules to bypass all .ir domains.
  537. ]
  538. },
  539. familyProtectDNS: {
  540. "servers": [
  541. "1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
  542. "1.0.0.3",
  543. "94.140.14.15", // https://adguard-dns.io/kb/general/dns-providers/
  544. "94.140.15.16"
  545. ],
  546. "queryStrategy": "UseIPv4"
  547. },
  548. }
  549. },
  550. methods: {
  551. loading(spinning = true) {
  552. this.spinning = spinning;
  553. },
  554. async getXraySetting() {
  555. this.loading(true);
  556. const msg = await HttpUtil.post("/panel/xray/");
  557. this.loading(false);
  558. if (msg.success) {
  559. result = JSON.parse(msg.obj);
  560. xs = JSON.stringify(result.xraySetting, null, 2);
  561. this.oldXraySetting = xs;
  562. this.xraySetting = xs;
  563. this.inboundTags = result.inboundTags;
  564. this.saveBtnDisable = true;
  565. }
  566. },
  567. async updateXraySetting() {
  568. this.loading(true);
  569. const msg = await HttpUtil.post("/panel/xray/update", {xraySetting : this.xraySetting});
  570. this.loading(false);
  571. if (msg.success) {
  572. await this.getXraySetting();
  573. }
  574. },
  575. async restartXray() {
  576. this.loading(true);
  577. const msg = await HttpUtil.post("server/restartXrayService");
  578. this.loading(false);
  579. if (msg.success) {
  580. this.loading(true);
  581. }
  582. },
  583. async fetchUserSecret() {
  584. this.loading(true);
  585. const userMessage = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
  586. if (userMessage.success) {
  587. this.user = userMessage.obj;
  588. }
  589. this.loading(false);
  590. },
  591. async updateSecret() {
  592. this.loading(true);
  593. const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
  594. if (msg.success) {
  595. this.user = msg.obj;
  596. window.location.replace(basePath + "logout");
  597. }
  598. this.loading(false);
  599. await this.updateXraySetting();
  600. },
  601. generateRandomString(length) {
  602. var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
  603. let randomString = "";
  604. for (let i = 0; i < length; i++) {
  605. randomString += chars[Math.floor(Math.random() * chars.length)];
  606. }
  607. return randomString;
  608. },
  609. async getNewSecret() {
  610. this.loading(true);
  611. await PromiseUtil.sleep(600);
  612. const newSecret = this.generateRandomString(64);
  613. this.user.loginSecret = newSecret;
  614. document.getElementById("token").textContent = newSecret;
  615. this.loading(false);
  616. },
  617. async toggleToken(value) {
  618. if (value) {
  619. await this.getNewSecret();
  620. } else {
  621. this.user.loginSecret = "";
  622. }
  623. },
  624. async resetXrayConfigToDefault() {
  625. this.loading(true);
  626. const msg = await HttpUtil.get("/panel/setting/getDefaultJsonConfig");
  627. this.loading(false);
  628. if (msg.success) {
  629. this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
  630. this.saveBtnDisable = true;
  631. }
  632. },
  633. syncRulesWithOutbound(tag, setting) {
  634. const newTemplateSettings = {...this.templateSettings};
  635. const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
  636. const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
  637. if (!haveRules && outboundIndex >= 0) {
  638. newTemplateSettings.outbounds.splice(outboundIndex, 1);
  639. }
  640. if (haveRules && outboundIndex === -1) {
  641. newTemplateSettings.outbounds.push(setting);
  642. }
  643. this.templateSettings = newTemplateSettings;
  644. },
  645. templateRuleGetter(routeSettings) {
  646. const { property, outboundTag } = routeSettings;
  647. let result = [];
  648. if (this.templateSettings != null) {
  649. this.templateSettings.routing.rules.forEach(
  650. (routingRule) => {
  651. if (
  652. routingRule.hasOwnProperty(property) &&
  653. routingRule.hasOwnProperty("outboundTag") &&
  654. routingRule.outboundTag === outboundTag
  655. ) {
  656. result.push(...routingRule[property]);
  657. }
  658. }
  659. );
  660. }
  661. return result;
  662. },
  663. templateRuleSetter(routeSettings) {
  664. const { data, property, outboundTag } = routeSettings;
  665. const oldTemplateSettings = this.templateSettings;
  666. const newTemplateSettings = oldTemplateSettings;
  667. currentProperty = this.templateRuleGetter({ outboundTag, property })
  668. if (currentProperty.length == 0) {
  669. const propertyRule = {
  670. type: "field",
  671. outboundTag,
  672. [property]: data
  673. };
  674. newTemplateSettings.routing.rules.push(propertyRule);
  675. }
  676. else {
  677. const newRules = [];
  678. insertedOnce = false;
  679. newTemplateSettings.routing.rules.forEach(
  680. (routingRule) => {
  681. if (
  682. routingRule.hasOwnProperty(property) &&
  683. routingRule.hasOwnProperty("outboundTag") &&
  684. routingRule.outboundTag === outboundTag
  685. ) {
  686. if (!insertedOnce && data.length > 0) {
  687. insertedOnce = true;
  688. routingRule[property] = data;
  689. newRules.push(routingRule);
  690. }
  691. }
  692. else {
  693. newRules.push(routingRule);
  694. }
  695. }
  696. );
  697. newTemplateSettings.routing.rules = newRules;
  698. }
  699. this.templateSettings = newTemplateSettings;
  700. },
  701. changeCode() {
  702. if(this.cm != null) {
  703. this.cm.toTextArea();
  704. }
  705. textAreaObj = document.getElementById('xraySetting');
  706. textAreaObj.value = this[this.advSettings];
  707. this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
  708. this.cm.on('change',editor => {
  709. value = editor.getValue();
  710. if(this.isJsonString(value)){
  711. this[this.advSettings] = value;
  712. }
  713. });
  714. },
  715. isJsonString(str) {
  716. try {
  717. JSON.parse(str);
  718. } catch (e) {
  719. return false;
  720. }
  721. return true;
  722. },
  723. findOutboundAddress(o) {
  724. serverObj = null;
  725. switch(o.protocol){
  726. case Protocols.VMess:
  727. case Protocols.VLESS:
  728. serverObj = o.settings.vnext;
  729. break;
  730. case Protocols.HTTP:
  731. case Protocols.Socks:
  732. case Protocols.Shadowsocks:
  733. case Protocols.Trojan:
  734. serverObj = o.settings.servers;
  735. break;
  736. case Protocols.DNS:
  737. return [o.settings.address + ':' + o.settings.port];
  738. default:
  739. return null;
  740. }
  741. return serverObj ? serverObj.map(obj => obj.address + ':' + obj.port) : null;
  742. },
  743. addOutbound(){
  744. outModal.show({
  745. title: '{{ i18n "pages.xray.outbound.addOutbound"}}',
  746. okText: '{{ i18n "pages.xray.outbound.addOutbound" }}',
  747. confirm: (outbound) => {
  748. outModal.loading();
  749. if(outbound.tag.length > 0){
  750. this.templateSettings.outbounds.push(outbound);
  751. this.outboundSettings = JSON.stringify(this.templateSettings.outbounds);
  752. }
  753. outModal.close();
  754. },
  755. isEdit: false,
  756. tags: this.templateSettings.outbounds.map(obj => obj.tag)
  757. });
  758. },
  759. editOutbound(index){
  760. outModal.show({
  761. title: '{{ i18n "pages.xray.outbound.editOutbound"}} ' + (index+1),
  762. outbound: app.templateSettings.outbounds[index],
  763. confirm: (outbound) => {
  764. outModal.loading();
  765. this.templateSettings.outbounds[index] = outbound;
  766. this.outboundSettings = JSON.stringify(this.templateSettings.outbounds);
  767. outModal.close();
  768. },
  769. isEdit: true,
  770. tags: this.outboundData.filter((o) => o.key != index ).map(obj => obj.tag)
  771. });
  772. },
  773. deleteOutbound(index){
  774. outbounds = this.templateSettings.outbounds;
  775. outbounds.splice(index,1);
  776. this.outboundSettings = JSON.stringify(outbounds);
  777. },
  778. addReverse(){
  779. reverseModal.show({
  780. title: '{{ i18n "pages.xray.outbound.addReverse"}}',
  781. okText: '{{ i18n "pages.xray.outbound.addReverse" }}',
  782. confirm: (reverse, rules) => {
  783. reverseModal.loading();
  784. if(reverse.tag.length > 0){
  785. newTemplateSettings = this.templateSettings;
  786. if(newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {};
  787. if(newTemplateSettings.reverse[reverse.type+'s'] == undefined) newTemplateSettings.reverse[reverse.type+'s'] = [];
  788. newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain });
  789. this.templateSettings = newTemplateSettings;
  790. // Add related rules
  791. this.templateSettings.routing.rules.push(...rules);
  792. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  793. }
  794. reverseModal.close();
  795. },
  796. isEdit: false
  797. });
  798. },
  799. editReverse(index){
  800. if(this.reverseData[index].type == "bridge") {
  801. oldRules = this.templateSettings.routing.rules.filter(r => r.inboundTag && r.inboundTag[0] == this.reverseData[index].tag);
  802. } else {
  803. oldRules = this.templateSettings.routing.rules.filter(r => r.outboundTag && r.outboundTag == this.reverseData[index].tag);
  804. }
  805. reverseModal.show({
  806. title: '{{ i18n "pages.xray.outbound.editReverse"}} ' + (index+1),
  807. reverse: this.reverseData[index],
  808. rules: oldRules,
  809. confirm: (reverse, rules) => {
  810. reverseModal.loading();
  811. if(reverse.tag.length > 0){
  812. oldtag = this.reverseData[index].tag;
  813. this.deleteReverse(index);
  814. newTemplateSettings = this.templateSettings;
  815. if(newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {};
  816. if(newTemplateSettings.reverse[reverse.type+'s'] == undefined) newTemplateSettings.reverse[reverse.type+'s'] = [];
  817. newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain });
  818. this.templateSettings = newTemplateSettings;
  819. // Adjust Rules
  820. newRules = this.templateSettings.routing.rules.filter(r => r.outboundTag != oldtag && (r.inboundTag && !r.inboundTag.includes(oldtag)));
  821. newRules.push(...rules)
  822. this.routingRuleSettings = JSON.stringify(newRules);
  823. }
  824. reverseModal.close();
  825. },
  826. isEdit: true
  827. });
  828. },
  829. deleteReverse(index){
  830. oldData = this.reverseData[index];
  831. newTemplateSettings = this.templateSettings;
  832. reverseTypeObj = newTemplateSettings.reverse[oldData.type+'s'];
  833. realIndex = reverseTypeObj.findIndex(r => r.tag==oldData.tag && r.domain==oldData.domain);
  834. newTemplateSettings.reverse[oldData.type+'s'].splice(realIndex,1);
  835. if(reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s');
  836. if(Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings, 'reverse');
  837. newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag && (r.inboundTag && !r.inboundTag.includes(oldData.tag)));
  838. newTemplateSettings.routing.rules = newRules;
  839. this.templateSettings = newTemplateSettings;
  840. },
  841. addRule(){
  842. ruleModal.show({
  843. title: '{{ i18n "pages.xray.rules.add"}}',
  844. okText: '{{ i18n "pages.xray.rules.add" }}',
  845. confirm: (rule) => {
  846. ruleModal.loading();
  847. if(JSON.stringify(rule).length > 3){
  848. this.templateSettings.routing.rules.push(rule);
  849. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  850. }
  851. ruleModal.close();
  852. },
  853. isEdit: false
  854. });
  855. },
  856. editRule(index){
  857. ruleModal.show({
  858. title: '{{ i18n "pages.xray.rules.edit"}} ' + (index+1),
  859. rule: app.templateSettings.routing.rules[index],
  860. confirm: (rule) => {
  861. ruleModal.loading();
  862. if(JSON.stringify(rule).length > 3){
  863. this.templateSettings.routing.rules[index] = rule;
  864. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  865. }
  866. ruleModal.close();
  867. },
  868. isEdit: true
  869. });
  870. },
  871. replaceRule(old_index,new_index){
  872. rules = this.templateSettings.routing.rules;
  873. if (new_index >= rules.length) rules.push(undefined);
  874. rules.splice(new_index, 0, rules.splice(old_index, 1)[0]);
  875. this.routingRuleSettings = JSON.stringify(rules);
  876. },
  877. deleteRule(index){
  878. rules = this.templateSettings.routing.rules;
  879. rules.splice(index,1);
  880. this.routingRuleSettings = JSON.stringify(rules);
  881. }
  882. },
  883. async mounted() {
  884. await this.getXraySetting();
  885. while (true) {
  886. await PromiseUtil.sleep(600);
  887. this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
  888. }
  889. },
  890. computed: {
  891. templateSettings: {
  892. get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; },
  893. set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); },
  894. },
  895. inboundSettings: {
  896. get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
  897. set: function (newValue) {
  898. newTemplateSettings = this.templateSettings;
  899. newTemplateSettings.inbounds = JSON.parse(newValue);
  900. this.templateSettings = newTemplateSettings;
  901. },
  902. },
  903. outboundSettings: {
  904. get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
  905. set: function (newValue) {
  906. newTemplateSettings = this.templateSettings;
  907. newTemplateSettings.outbounds = JSON.parse(newValue);
  908. this.templateSettings = newTemplateSettings;
  909. },
  910. },
  911. outboundData: {
  912. get: function () {
  913. data = []
  914. if (this.templateSettings != null) {
  915. this.templateSettings.outbounds.forEach((o, index) => {
  916. data.push({'key': index, ...o});
  917. });
  918. }
  919. return data;
  920. },
  921. },
  922. reverseData: {
  923. get: function () {
  924. data = []
  925. if (this.templateSettings != null && this.templateSettings.reverse != null) {
  926. if(this.templateSettings.reverse.bridges) {
  927. this.templateSettings.reverse.bridges.forEach((o, index) => {
  928. data.push({'key': index, 'type':'bridge', ...o});
  929. });
  930. }
  931. if(this.templateSettings.reverse.portals){
  932. this.templateSettings.reverse.portals.forEach((o, index) => {
  933. data.push({'key': index, 'type':'portal', ...o});
  934. });
  935. }
  936. }
  937. return data;
  938. },
  939. },
  940. routingRuleSettings: {
  941. get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
  942. set: function (newValue) {
  943. newTemplateSettings = this.templateSettings;
  944. newTemplateSettings.routing.rules = JSON.parse(newValue);
  945. this.templateSettings = newTemplateSettings;
  946. },
  947. },
  948. routingRuleData: {
  949. get: function () {
  950. data = [];
  951. if (this.templateSettings != null) {
  952. this.templateSettings.routing.rules.forEach((r, index) => {
  953. data.push({'key': index, ...r});
  954. });
  955. // Make rules readable
  956. data.forEach(r => {
  957. if(r.domain) r.domain = r.domain.join(',')
  958. if(r.ip) r.ip = r.ip.join(',')
  959. if(r.source) r.source = r.source.join(',');
  960. if(r.user) r.user = r.user.join(',')
  961. if(r.inboundTag) r.inboundTag = r.inboundTag.join(',')
  962. if(r.protocol) r.protocol = r.protocol.join(',')
  963. if(r.attrs) r.attrs = JSON.stringify(r.attrs, null, 2)
  964. });
  965. }
  966. return data;
  967. }
  968. },
  969. freedomStrategy: {
  970. get: function () {
  971. if (!this.templateSettings) return "AsIs";
  972. freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && o.tag == "direct");
  973. if (!freedomOutbound) return "AsIs";
  974. if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
  975. return freedomOutbound.settings.domainStrategy;
  976. },
  977. set: function (newValue) {
  978. newTemplateSettings = this.templateSettings;
  979. freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && o.tag == "direct");
  980. if(freedomOutboundIndex == -1){
  981. newTemplateSettings.outbounds.push({protocol: "freedom", tag: "direct", settings: { "domainStrategy": newValue }});
  982. } else if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) {
  983. newTemplateSettings.outbounds[freedomOutboundIndex].settings = {"domainStrategy": newValue};
  984. } else {
  985. newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue;
  986. }
  987. this.templateSettings = newTemplateSettings;
  988. }
  989. },
  990. routingStrategy: {
  991. get: function () {
  992. if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing.domainStrategy) return "AsIs";
  993. return this.templateSettings.routing.domainStrategy;
  994. },
  995. set: function (newValue) {
  996. newTemplateSettings = this.templateSettings;
  997. newTemplateSettings.routing.domainStrategy = newValue;
  998. this.templateSettings = newTemplateSettings;
  999. }
  1000. },
  1001. blockedIPs: {
  1002. get: function () {
  1003. return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
  1004. },
  1005. set: function (newValue) {
  1006. this.templateRuleSetter({ outboundTag: "blocked", property: "ip", data: newValue });
  1007. }
  1008. },
  1009. blockedDomains: {
  1010. get: function () {
  1011. return this.templateRuleGetter({ outboundTag: "blocked", property: "domain" });
  1012. },
  1013. set: function (newValue) {
  1014. this.templateRuleSetter({ outboundTag: "blocked", property: "domain", data: newValue });
  1015. }
  1016. },
  1017. blockedProtocols: {
  1018. get: function () {
  1019. return this.templateRuleGetter({ outboundTag: "blocked", property: "protocol" });
  1020. },
  1021. set: function (newValue) {
  1022. this.templateRuleSetter({ outboundTag: "blocked", property: "protocol", data: newValue });
  1023. }
  1024. },
  1025. directIPs: {
  1026. get: function () {
  1027. return this.templateRuleGetter({ outboundTag: "direct", property: "ip" });
  1028. },
  1029. set: function (newValue) {
  1030. this.templateRuleSetter({ outboundTag: "direct", property: "ip", data: newValue });
  1031. this.syncRulesWithOutbound("direct", this.directSettings);
  1032. }
  1033. },
  1034. directDomains: {
  1035. get: function () {
  1036. return this.templateRuleGetter({ outboundTag: "direct", property: "domain" });
  1037. },
  1038. set: function (newValue) {
  1039. this.templateRuleSetter({ outboundTag: "direct", property: "domain", data: newValue });
  1040. this.syncRulesWithOutbound("direct", this.directSettings);
  1041. }
  1042. },
  1043. ipv4Domains: {
  1044. get: function () {
  1045. return this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
  1046. },
  1047. set: function (newValue) {
  1048. this.templateRuleSetter({ outboundTag: "IPv4", property: "domain", data: newValue });
  1049. this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
  1050. }
  1051. },
  1052. warpDomains: {
  1053. get: function () {
  1054. return this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
  1055. },
  1056. set: function (newValue) {
  1057. this.templateRuleSetter({ outboundTag: "WARP", property: "domain", data: newValue });
  1058. this.syncRulesWithOutbound("WARP", this.warpSettings);
  1059. }
  1060. },
  1061. manualBlockedIPs: {
  1062. get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
  1063. set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
  1064. },
  1065. manualBlockedDomains: {
  1066. get: function () { return JSON.stringify(this.blockedDomains, null, 2); },
  1067. set: debounce(function (value) { this.blockedDomains = JSON.parse(value); }, 1000)
  1068. },
  1069. manualDirectIPs: {
  1070. get: function () { return JSON.stringify(this.directIPs, null, 2); },
  1071. set: debounce(function (value) { this.directIPs = JSON.parse(value); }, 1000)
  1072. },
  1073. manualDirectDomains: {
  1074. get: function () { return JSON.stringify(this.directDomains, null, 2); },
  1075. set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
  1076. },
  1077. manualIPv4Domains: {
  1078. get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
  1079. set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
  1080. },
  1081. manualWARPDomains: {
  1082. get: function () { return JSON.stringify(this.warpDomains, null, 2); },
  1083. set: debounce(function (value) { this.warpDomains = JSON.parse(value); }, 1000)
  1084. },
  1085. torrentSettings: {
  1086. get: function () {
  1087. return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
  1088. },
  1089. set: function (newValue) {
  1090. if (newValue) {
  1091. this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent];
  1092. } else {
  1093. this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent.includes(data));
  1094. }
  1095. },
  1096. },
  1097. privateIpSettings: {
  1098. get: function () {
  1099. return doAllItemsExist(this.settingsData.ips.local, this.blockedIPs);
  1100. },
  1101. set: function (newValue) {
  1102. if (newValue) {
  1103. this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local];
  1104. } else {
  1105. this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data));
  1106. }
  1107. },
  1108. },
  1109. AdsSettings: {
  1110. get: function () {
  1111. return doAllItemsExist(this.settingsData.domains.ads, this.blockedDomains);
  1112. },
  1113. set: function (newValue) {
  1114. if (newValue) {
  1115. this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads];
  1116. } else {
  1117. this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data));
  1118. }
  1119. },
  1120. },
  1121. SpeedTestSettings: {
  1122. get: function () {
  1123. return doAllItemsExist(this.settingsData.domains.speedtest, this.blockedDomains);
  1124. },
  1125. set: function (newValue) {
  1126. if (newValue) {
  1127. this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.speedtest];
  1128. } else {
  1129. this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.speedtest.includes(data));
  1130. }
  1131. },
  1132. },
  1133. familyProtectSettings: {
  1134. get: function () {
  1135. if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
  1136. return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
  1137. },
  1138. set: function (newValue) {
  1139. newTemplateSettings = this.templateSettings;
  1140. if (newValue) {
  1141. newTemplateSettings.dns = this.settingsData.familyProtectDNS;
  1142. } else {
  1143. delete newTemplateSettings.dns;
  1144. }
  1145. this.templateSettings = newTemplateSettings;
  1146. },
  1147. },
  1148. GoogleIPv4Settings: {
  1149. get: function () {
  1150. return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
  1151. },
  1152. set: function (newValue) {
  1153. if (newValue) {
  1154. this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
  1155. } else {
  1156. this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
  1157. }
  1158. },
  1159. },
  1160. NetflixIPv4Settings: {
  1161. get: function () {
  1162. return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
  1163. },
  1164. set: function (newValue) {
  1165. if (newValue) {
  1166. this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
  1167. } else {
  1168. this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
  1169. }
  1170. },
  1171. },
  1172. IRIpSettings: {
  1173. get: function () {
  1174. return doAllItemsExist(this.settingsData.ips.ir, this.blockedIPs);
  1175. },
  1176. set: function (newValue) {
  1177. if (newValue) {
  1178. this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir];
  1179. } else {
  1180. this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data));
  1181. }
  1182. }
  1183. },
  1184. IRDomainSettings: {
  1185. get: function () {
  1186. return doAllItemsExist(this.settingsData.domains.ir, this.blockedDomains);
  1187. },
  1188. set: function (newValue) {
  1189. if (newValue) {
  1190. this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir];
  1191. } else {
  1192. this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data));
  1193. }
  1194. }
  1195. },
  1196. ChinaIpSettings: {
  1197. get: function () {
  1198. return doAllItemsExist(this.settingsData.ips.cn, this.blockedIPs);
  1199. },
  1200. set: function (newValue) {
  1201. if (newValue) {
  1202. this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn];
  1203. } else {
  1204. this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data));
  1205. }
  1206. }
  1207. },
  1208. ChinaDomainSettings: {
  1209. get: function () {
  1210. return doAllItemsExist(this.settingsData.domains.cn, this.blockedDomains);
  1211. },
  1212. set: function (newValue) {
  1213. if (newValue) {
  1214. this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn];
  1215. } else {
  1216. this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data));
  1217. }
  1218. }
  1219. },
  1220. RussiaIpSettings: {
  1221. get: function () {
  1222. return doAllItemsExist(this.settingsData.ips.ru, this.blockedIPs);
  1223. },
  1224. set: function (newValue) {
  1225. if (newValue) {
  1226. this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru];
  1227. } else {
  1228. this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data));
  1229. }
  1230. }
  1231. },
  1232. RussiaDomainSettings: {
  1233. get: function () {
  1234. return doAllItemsExist(this.settingsData.domains.ru, this.blockedDomains);
  1235. },
  1236. set: function (newValue) {
  1237. if (newValue) {
  1238. this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru];
  1239. } else {
  1240. this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data));
  1241. }
  1242. }
  1243. },
  1244. IRIpDirectSettings: {
  1245. get: function () {
  1246. return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
  1247. },
  1248. set: function (newValue) {
  1249. if (newValue) {
  1250. this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir];
  1251. } else {
  1252. this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data));
  1253. }
  1254. }
  1255. },
  1256. IRDomainDirectSettings: {
  1257. get: function () {
  1258. return doAllItemsExist(this.settingsData.domains.ir, this.directDomains);
  1259. },
  1260. set: function (newValue) {
  1261. if (newValue) {
  1262. this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir];
  1263. } else {
  1264. this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data));
  1265. }
  1266. }
  1267. },
  1268. ChinaIpDirectSettings: {
  1269. get: function () {
  1270. return doAllItemsExist(this.settingsData.ips.cn, this.directIPs);
  1271. },
  1272. set: function (newValue) {
  1273. if (newValue) {
  1274. this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn];
  1275. } else {
  1276. this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data));
  1277. }
  1278. }
  1279. },
  1280. ChinaDomainDirectSettings: {
  1281. get: function () {
  1282. return doAllItemsExist(this.settingsData.domains.cn, this.directDomains);
  1283. },
  1284. set: function (newValue) {
  1285. if (newValue) {
  1286. this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn];
  1287. } else {
  1288. this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data));
  1289. }
  1290. }
  1291. },
  1292. RussiaIpDirectSettings: {
  1293. get: function () {
  1294. return doAllItemsExist(this.settingsData.ips.ru, this.directIPs);
  1295. },
  1296. set: function (newValue) {
  1297. if (newValue) {
  1298. this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru];
  1299. } else {
  1300. this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data));
  1301. }
  1302. }
  1303. },
  1304. RussiaDomainDirectSettings: {
  1305. get: function () {
  1306. return doAllItemsExist(this.settingsData.domains.ru, this.directDomains);
  1307. },
  1308. set: function (newValue) {
  1309. if (newValue) {
  1310. this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru];
  1311. } else {
  1312. this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data));
  1313. }
  1314. }
  1315. },
  1316. GoogleWARPSettings: {
  1317. get: function () {
  1318. return doAllItemsExist(this.settingsData.domains.google, this.warpDomains);
  1319. },
  1320. set: function (newValue) {
  1321. if (newValue) {
  1322. this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.google];
  1323. } else {
  1324. this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.google.includes(data));
  1325. }
  1326. },
  1327. },
  1328. OpenAIWARPSettings: {
  1329. get: function () {
  1330. return doAllItemsExist(this.settingsData.domains.openai, this.warpDomains);
  1331. },
  1332. set: function (newValue) {
  1333. if (newValue) {
  1334. this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.openai];
  1335. } else {
  1336. this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.openai.includes(data));
  1337. }
  1338. },
  1339. },
  1340. NetflixWARPSettings: {
  1341. get: function () {
  1342. return doAllItemsExist(this.settingsData.domains.netflix, this.warpDomains);
  1343. },
  1344. set: function (newValue) {
  1345. if (newValue) {
  1346. this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.netflix];
  1347. } else {
  1348. this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.netflix.includes(data));
  1349. }
  1350. },
  1351. },
  1352. SpotifyWARPSettings: {
  1353. get: function () {
  1354. return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
  1355. },
  1356. set: function (newValue) {
  1357. if (newValue) {
  1358. this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.spotify];
  1359. } else {
  1360. this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.spotify.includes(data));
  1361. }
  1362. },
  1363. },
  1364. },
  1365. });
  1366. </script>
  1367. </body>
  1368. </html>