xray.html 78 KB

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