xray.html 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139
  1. {{ template "page/head_start" .}}
  2. <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.min.css?{{ .cur_ver }}">
  3. <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
  4. <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}">
  5. <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
  6. {{ template "page/head_end" .}}
  7. {{ template "page/body_start" .}}
  8. <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' xray-page'">
  9. <a-sidebar></a-sidebar>
  10. <a-layout id="content-layout">
  11. <a-layout-content>
  12. <a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}' size="large">
  13. <transition name="list" appear>
  14. <a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }"
  15. message='{{ i18n "secAlertTitle" }}' color="red" description='{{ i18n "secAlertSsl" }}' show-icon closable>
  16. </a-alert>
  17. </transition>
  18. <transition name="list" appear>
  19. <a-row v-if="!loadingStates.fetched">
  20. <div :style="{ minHeight: 'calc(100vh - 120px)' }"></div>
  21. </a-row>
  22. <a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-else>
  23. <a-col>
  24. <a-card hoverable>
  25. <a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }">
  26. <a-col :xs="24" :sm="10" :style="{ padding: '4px' }">
  27. <a-space direction="horizontal">
  28. <a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">
  29. {{ i18n "pages.xray.save" }}
  30. </a-button>
  31. <a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">
  32. {{ i18n "pages.xray.restart" }}
  33. </a-button>
  34. <a-popover v-if="restartResult" :overlay-class-name="themeSwitcher.currentTheme">
  35. <span slot="title">{{ i18n
  36. "pages.index.xrayErrorPopoverTitle" }}</span>
  37. <template slot="content">
  38. <span :style="{ maxWidth: '400px' }" v-for="line in restartResult.split('\n')">[[ line
  39. ]]</span>
  40. </template>
  41. <a-icon type="question-circle"></a-icon>
  42. </a-popover>
  43. </a-space>
  44. </a-col>
  45. <a-col :xs="24" :sm="14">
  46. <template>
  47. <div>
  48. <a-back-top :target="() => document.getElementById('content-layout')"
  49. visibility-height="200"></a-back-top>
  50. <a-alert type="warning" :style="{ float: 'right', width: 'fit-content' }"
  51. message='{{ i18n "pages.settings.infoDesc" }}' show-icon>
  52. </a-alert>
  53. </div>
  54. </template>
  55. </a-col>
  56. </a-row>
  57. </a-card>
  58. </a-col>
  59. <a-col>
  60. <a-tabs default-active-key="tpl-basic" @change="(activeKey) => { this.changePage(activeKey); }"
  61. :class="themeSwitcher.currentTheme">
  62. <a-tab-pane key="tpl-basic" :style="{ paddingTop: '20px' }">
  63. <template #tab>
  64. <a-icon type="setting"></a-icon>
  65. <span>{{ i18n "pages.xray.basicTemplate"}}</span>
  66. </template>
  67. {{ template "settings/xray/basics" . }}
  68. </a-tab-pane>
  69. <a-tab-pane key="tpl-routing" :style="{ paddingTop: '20px' }">
  70. <template #tab>
  71. <a-icon type="swap"></a-icon>
  72. <span>{{ i18n "pages.xray.Routings"}}</span>
  73. </template>
  74. {{ template "settings/xray/routing" . }}
  75. </a-tab-pane>
  76. <a-tab-pane key="tpl-outbound" force-render="true">
  77. <template #tab>
  78. <a-icon type="upload"></a-icon>
  79. <span>{{ i18n "pages.xray.Outbounds"}}</span>
  80. </template>
  81. {{ template "settings/xray/outbounds" . }}
  82. </a-tab-pane>
  83. <a-tab-pane key="tpl-reverse" :style="{ paddingTop: '20px' }" force-render="true">
  84. <template #tab>
  85. <a-icon type="import"></a-icon>
  86. <span>{{ i18n "pages.xray.outbound.reverse"}}</span>
  87. </template>
  88. {{ template "settings/xray/reverse" . }}
  89. </a-tab-pane>
  90. <a-tab-pane key="tpl-balancer" :style="{ paddingTop: '20px' }" force-render="true">
  91. <template #tab>
  92. <a-icon type="cluster"></a-icon>
  93. <span>{{ i18n "pages.xray.Balancers"}}</span>
  94. </template>
  95. {{ template "settings/xray/balancers" . }}
  96. </a-tab-pane>
  97. <a-tab-pane key="tpl-dns" :style="{ paddingTop: '20px' }" force-render="true">
  98. <template #tab>
  99. <a-icon type="database"></a-icon>
  100. <span>DNS</span>
  101. </template>
  102. {{ template "settings/xray/dns" . }}
  103. </a-tab-pane>
  104. <a-tab-pane key="tpl-advanced" force-render="true">
  105. <template #tab>
  106. <a-icon type="code"></a-icon>
  107. <span>{{ i18n "pages.xray.advancedTemplate"}}</span>
  108. </template>
  109. {{ template "settings/xray/advanced" . }}
  110. </a-tab-pane>
  111. </a-tabs>
  112. </a-col>
  113. </a-row>
  114. </transition>
  115. </a-spin>
  116. </a-layout-content>
  117. </a-layout>
  118. </a-layout>
  119. {{template "page/body_scripts" .}}
  120. <script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
  121. <script src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script>
  122. <script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
  123. <script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
  124. <script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
  125. <script src="{{ .base_path }}assets/codemirror/lint/lint.js"></script>
  126. <script src="{{ .base_path }}assets/codemirror/lint/javascript-lint.js"></script>
  127. <script src="{{ .base_path }}assets/codemirror/hint/javascript-hint.js"></script>
  128. <script src="{{ .base_path }}assets/codemirror/fold/foldcode.js"></script>
  129. <script src="{{ .base_path }}assets/codemirror/fold/foldgutter.js"></script>
  130. <script src="{{ .base_path }}assets/codemirror/fold/brace-fold.js"></script>
  131. {{template "component/aSidebar" .}}
  132. {{template "component/aThemeSwitch" .}}
  133. {{template "component/aTableSortable" .}}
  134. {{template "component/aSettingListItem" .}}
  135. {{template "modals/ruleModal" .}}
  136. {{template "modals/outModal" .}}
  137. {{template "modals/reverseModal" .}}
  138. {{template "modals/balancerModal" .}}
  139. {{template "modals/dnsModal" .}}
  140. {{template "modals/dnsPresetsModal" .}}
  141. {{template "modals/fakednsModal" .}}
  142. {{template "modals/warpModal" .}}
  143. {{template "modals/nordModal" .}}
  144. <script>
  145. const rulesColumns = [{
  146. title: "#",
  147. align: 'center',
  148. width: 15,
  149. scopedSlots: {
  150. customRender: 'action'
  151. }
  152. },
  153. {
  154. title: '{{ i18n "pages.xray.rules.source"}}',
  155. children: [{
  156. title: 'IP',
  157. dataIndex: "sourceIP",
  158. align: 'center',
  159. width: 20,
  160. ellipsis: true
  161. },
  162. {
  163. title: '{{ i18n "pages.inbounds.port" }}',
  164. dataIndex: 'sourcePort',
  165. align: 'center',
  166. width: 10,
  167. ellipsis: true
  168. },
  169. {
  170. title: 'VLESS Route',
  171. dataIndex: 'vlessRoute',
  172. align: 'center',
  173. width: 15,
  174. ellipsis: true
  175. }
  176. ]
  177. },
  178. {
  179. title: '{{ i18n "pages.inbounds.network"}}',
  180. children: [{
  181. title: 'L4',
  182. dataIndex: 'network',
  183. align: 'center',
  184. width: 10
  185. },
  186. {
  187. title: '{{ i18n "protocol" }}',
  188. dataIndex: 'protocol',
  189. align: 'center',
  190. width: 15,
  191. ellipsis: true
  192. },
  193. {
  194. title: 'Attrs',
  195. dataIndex: 'attrs',
  196. align: 'center',
  197. width: 10,
  198. ellipsis: true
  199. }
  200. ]
  201. },
  202. {
  203. title: '{{ i18n "pages.xray.rules.dest"}}',
  204. children: [{
  205. title: 'IP',
  206. dataIndex: 'ip',
  207. align: 'center',
  208. width: 20,
  209. ellipsis: true
  210. },
  211. {
  212. title: '{{ i18n "pages.xray.outbound.domain" }}',
  213. dataIndex: 'domain',
  214. align: 'center',
  215. width: 20,
  216. ellipsis: true
  217. },
  218. {
  219. title: '{{ i18n "pages.inbounds.port" }}',
  220. dataIndex: 'port',
  221. align: 'center',
  222. width: 10,
  223. ellipsis: true
  224. }
  225. ]
  226. },
  227. {
  228. title: '{{ i18n "pages.xray.rules.inbound"}}',
  229. children: [{
  230. title: '{{ i18n "pages.xray.outbound.tag" }}',
  231. dataIndex: 'inboundTag',
  232. align: 'center',
  233. width: 15,
  234. ellipsis: true
  235. },
  236. {
  237. title: '{{ i18n "pages.inbounds.client" }}',
  238. dataIndex: 'user',
  239. align: 'center',
  240. width: 20,
  241. ellipsis: true
  242. }
  243. ]
  244. },
  245. {
  246. title: '{{ i18n "pages.xray.rules.outbound"}}',
  247. dataIndex: 'outboundTag',
  248. align: 'center',
  249. width: 17
  250. },
  251. {
  252. title: '{{ i18n "pages.xray.rules.balancer"}}',
  253. dataIndex: 'balancerTag',
  254. align: 'center',
  255. width: 15
  256. },
  257. ];
  258. const rulesMobileColumns = [{
  259. title: "#",
  260. align: 'center',
  261. width: 20,
  262. scopedSlots: {
  263. customRender: 'action'
  264. }
  265. },
  266. {
  267. title: '{{ i18n "pages.xray.rules.inbound"}}',
  268. align: 'center',
  269. width: 50,
  270. ellipsis: true,
  271. scopedSlots: {
  272. customRender: 'inbound'
  273. }
  274. },
  275. {
  276. title: '{{ i18n "pages.xray.rules.outbound"}}',
  277. align: 'center',
  278. width: 50,
  279. ellipsis: true,
  280. scopedSlots: {
  281. customRender: 'outbound'
  282. }
  283. },
  284. {
  285. title: '{{ i18n "pages.xray.rules.info"}}',
  286. align: 'center',
  287. width: 50,
  288. ellipsis: true,
  289. scopedSlots: {
  290. customRender: 'info'
  291. }
  292. },
  293. ];
  294. const outboundColumns = [{
  295. title: "#",
  296. align: 'center',
  297. width: 60,
  298. scopedSlots: {
  299. customRender: 'action'
  300. }
  301. },
  302. {
  303. title: '{{ i18n "pages.xray.outbound.tag"}}',
  304. dataIndex: 'tag',
  305. align: 'center',
  306. width: 50
  307. },
  308. {
  309. title: '{{ i18n "protocol"}}',
  310. align: 'center',
  311. width: 50,
  312. scopedSlots: {
  313. customRender: 'protocol'
  314. }
  315. },
  316. {
  317. title: '{{ i18n "pages.xray.outbound.address"}}',
  318. align: 'center',
  319. width: 50,
  320. scopedSlots: {
  321. customRender: 'address'
  322. }
  323. },
  324. {
  325. title: '{{ i18n "pages.inbounds.traffic" }}',
  326. align: 'center',
  327. width: 50,
  328. scopedSlots: {
  329. customRender: 'traffic'
  330. }
  331. },
  332. {
  333. title: '{{ i18n "pages.xray.outbound.testResult" }}',
  334. align: 'center',
  335. width: 120,
  336. scopedSlots: {
  337. customRender: 'testResult'
  338. }
  339. },
  340. {
  341. title: '{{ i18n "pages.xray.outbound.test" }}',
  342. align: 'center',
  343. width: 60,
  344. scopedSlots: {
  345. customRender: 'test'
  346. }
  347. },
  348. ];
  349. const reverseColumns = [{
  350. title: "#",
  351. align: 'center',
  352. width: 20,
  353. scopedSlots: {
  354. customRender: 'action'
  355. }
  356. },
  357. {
  358. title: '{{ i18n "pages.xray.outbound.type"}}',
  359. dataIndex: 'type',
  360. align: 'center',
  361. width: 50
  362. },
  363. {
  364. title: '{{ i18n "pages.xray.outbound.tag"}}',
  365. dataIndex: 'tag',
  366. align: 'center',
  367. width: 50
  368. },
  369. {
  370. title: '{{ i18n "pages.xray.outbound.domain"}}',
  371. dataIndex: 'domain',
  372. align: 'center',
  373. width: 50
  374. },
  375. ];
  376. const balancerColumns = [{
  377. title: "#",
  378. align: 'center',
  379. width: 20,
  380. scopedSlots: {
  381. customRender: 'action'
  382. }
  383. },
  384. {
  385. title: '{{ i18n "pages.xray.balancer.tag"}}',
  386. dataIndex: 'tag',
  387. align: 'center',
  388. width: 50
  389. },
  390. {
  391. title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}',
  392. align: 'center',
  393. width: 50,
  394. scopedSlots: {
  395. customRender: 'strategy'
  396. }
  397. },
  398. {
  399. title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}',
  400. align: 'center',
  401. width: 100,
  402. scopedSlots: {
  403. customRender: 'selector'
  404. }
  405. },
  406. ];
  407. const dnsColumns = [{
  408. title: "#",
  409. align: 'center',
  410. width: 20,
  411. scopedSlots: {
  412. customRender: 'action'
  413. }
  414. },
  415. {
  416. title: '{{ i18n "pages.xray.outbound.address"}}',
  417. align: 'center',
  418. width: 50,
  419. scopedSlots: {
  420. customRender: 'address'
  421. }
  422. },
  423. {
  424. title: '{{ i18n "pages.xray.dns.domains"}}',
  425. align: 'center',
  426. width: 50,
  427. scopedSlots: {
  428. customRender: 'domain'
  429. }
  430. },
  431. {
  432. title: '{{ i18n "pages.xray.dns.expectIPs"}}',
  433. align: 'center',
  434. width: 50,
  435. scopedSlots: {
  436. customRender: 'expectIPs'
  437. }
  438. },
  439. ];
  440. const fakednsColumns = [{
  441. title: "#",
  442. align: 'center',
  443. width: 20,
  444. scopedSlots: {
  445. customRender: 'action'
  446. }
  447. },
  448. {
  449. title: '{{ i18n "pages.xray.fakedns.ipPool"}}',
  450. dataIndex: 'ipPool',
  451. align: 'center',
  452. width: 50
  453. },
  454. {
  455. title: '{{ i18n "pages.xray.fakedns.poolSize"}}',
  456. dataIndex: 'poolSize',
  457. align: 'center',
  458. width: 50
  459. },
  460. ];
  461. const app = new Vue({
  462. delimiters: ['[[', ']]'],
  463. mixins: [MediaQueryMixin],
  464. el: '#app',
  465. data: {
  466. themeSwitcher,
  467. isDarkTheme: themeSwitcher.isDarkTheme,
  468. loadingStates: {
  469. fetched: false,
  470. spinning: false
  471. },
  472. oldXraySetting: '',
  473. xraySetting: '',
  474. outboundTestUrl: 'https://www.google.com/generate_204',
  475. oldOutboundTestUrl: 'https://www.google.com/generate_204',
  476. inboundTags: [],
  477. outboundsTraffic: [],
  478. outboundTestStates: {}, // Track testing state and results for each outbound
  479. saveBtnDisable: true,
  480. refreshing: false,
  481. restartResult: '',
  482. showAlert: false,
  483. customGeoAliasLabelSuffix: '{{ i18n "pages.index.customGeoAliasLabelSuffix" }}',
  484. advSettings: 'xraySetting',
  485. obsSettings: '',
  486. cm: null,
  487. cmOptions: {
  488. lineNumbers: true,
  489. mode: "application/json",
  490. lint: true,
  491. styleActiveLine: true,
  492. matchBrackets: true,
  493. theme: "xq",
  494. autoCloseTags: true,
  495. lineWrapping: true,
  496. indentUnit: 2,
  497. indentWithTabs: true,
  498. smartIndent: true,
  499. tabSize: 2,
  500. lineWiseCopyCut: false,
  501. foldGutter: true,
  502. gutters: [
  503. "CodeMirror-lint-markers",
  504. "CodeMirror-linenumbers",
  505. "CodeMirror-foldgutter",
  506. ],
  507. },
  508. ipv4Settings: {
  509. tag: "IPv4",
  510. protocol: "freedom",
  511. settings: {
  512. domainStrategy: "UseIPv4"
  513. }
  514. },
  515. directSettings: {
  516. tag: "direct",
  517. protocol: "freedom"
  518. },
  519. routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
  520. log: {
  521. loglevel: ["none", "debug", "info", "warning", "error"],
  522. access: ["none", "./access.log"],
  523. error: ["none", "./error.log"],
  524. dnsLog: false,
  525. maskAddress: ["quarter", "half", "full"],
  526. },
  527. settingsData: {
  528. protocols: {
  529. bittorrent: ["bittorrent"],
  530. },
  531. IPsOptions: [{
  532. label: 'Private IPs',
  533. value: 'geoip:private'
  534. },
  535. {
  536. label: '🇮🇷 Iran',
  537. value: 'ext:geoip_IR.dat:ir'
  538. },
  539. {
  540. label: '🇨🇳 China',
  541. value: 'geoip:cn'
  542. },
  543. {
  544. label: '🇷🇺 Russia',
  545. value: 'ext:geoip_RU.dat:ru'
  546. },
  547. {
  548. label: '🇻🇳 Vietnam',
  549. value: 'geoip:vn'
  550. },
  551. {
  552. label: '🇪🇸 Spain',
  553. value: 'geoip:es'
  554. },
  555. {
  556. label: '🇮🇩 Indonesia',
  557. value: 'geoip:id'
  558. },
  559. {
  560. label: '🇺🇦 Ukraine',
  561. value: 'geoip:ua'
  562. },
  563. {
  564. label: '🇹🇷 Türkiye',
  565. value: 'geoip:tr'
  566. },
  567. {
  568. label: '🇧🇷 Brazil',
  569. value: 'geoip:br'
  570. },
  571. ],
  572. DomainsOptions: [{
  573. label: '🇮🇷 Iran',
  574. value: 'ext:geosite_IR.dat:ir'
  575. },
  576. {
  577. label: '🇮🇷 .ir',
  578. value: 'regexp:.*\\.ir$'
  579. },
  580. {
  581. label: '🇮🇷 .ایران',
  582. value: 'regexp:.*\\.xn--mgba3a4f16a$'
  583. },
  584. {
  585. label: '🇨🇳 China',
  586. value: 'geosite:cn'
  587. },
  588. {
  589. label: '🇨🇳 .cn',
  590. value: 'regexp:.*\\.cn$'
  591. },
  592. {
  593. label: '🇷🇺 Russia',
  594. value: 'ext:geosite_RU.dat:ru-available-only-inside'
  595. },
  596. {
  597. label: '🇷🇺 .ru',
  598. value: 'regexp:.*\\.ru$'
  599. },
  600. {
  601. label: '🇷🇺 .su',
  602. value: 'regexp:.*\\.su$'
  603. },
  604. {
  605. label: '🇷🇺 .рф',
  606. value: 'regexp:.*\\.xn--p1ai$'
  607. },
  608. {
  609. label: '🇻🇳 .vn',
  610. value: 'regexp:.*\\.vn$'
  611. },
  612. ],
  613. BlockDomainsOptions: [{
  614. label: 'Ads All',
  615. value: 'geosite:category-ads-all'
  616. },
  617. {
  618. label: 'Ads IR 🇮🇷',
  619. value: 'ext:geosite_IR.dat:category-ads-all'
  620. },
  621. {
  622. label: 'Ads RU 🇷🇺',
  623. value: 'ext:geosite_RU.dat:category-ads-all'
  624. },
  625. {
  626. label: 'Malware 🇮🇷',
  627. value: 'ext:geosite_IR.dat:malware'
  628. },
  629. {
  630. label: 'Phishing 🇮🇷',
  631. value: 'ext:geosite_IR.dat:phishing'
  632. },
  633. {
  634. label: 'Cryptominers 🇮🇷',
  635. value: 'ext:geosite_IR.dat:cryptominers'
  636. },
  637. {
  638. label: 'Adult +18',
  639. value: 'geosite:category-porn'
  640. },
  641. {
  642. label: '🇮🇷 Iran',
  643. value: 'ext:geosite_IR.dat:ir'
  644. },
  645. {
  646. label: '🇮🇷 .ir',
  647. value: 'regexp:.*\\.ir$'
  648. },
  649. {
  650. label: '🇮🇷 .ایران',
  651. value: 'regexp:.*\\.xn--mgba3a4f16a$'
  652. },
  653. {
  654. label: '🇨🇳 China',
  655. value: 'geosite:cn'
  656. },
  657. {
  658. label: '🇨🇳 .cn',
  659. value: 'regexp:.*\\.cn$'
  660. },
  661. {
  662. label: '🇷🇺 Russia',
  663. value: 'ext:geosite_RU.dat:ru-available-only-inside'
  664. },
  665. {
  666. label: '🇷🇺 .ru',
  667. value: 'regexp:.*\\.ru$'
  668. },
  669. {
  670. label: '🇷🇺 .su',
  671. value: 'regexp:.*\\.su$'
  672. },
  673. {
  674. label: '🇷🇺 .рф',
  675. value: 'regexp:.*\\.xn--p1ai$'
  676. },
  677. {
  678. label: '🇻🇳 .vn',
  679. value: 'regexp:.*\\.vn$'
  680. },
  681. ],
  682. ServicesOptions: [{
  683. label: 'Apple',
  684. value: 'geosite:apple'
  685. },
  686. {
  687. label: 'Meta',
  688. value: 'geosite:meta'
  689. },
  690. {
  691. label: 'Google',
  692. value: 'geosite:google'
  693. },
  694. {
  695. label: 'OpenAI',
  696. value: 'geosite:openai'
  697. },
  698. {
  699. label: 'Spotify',
  700. value: 'geosite:spotify'
  701. },
  702. {
  703. label: 'Netflix',
  704. value: 'geosite:netflix'
  705. },
  706. {
  707. label: 'Reddit',
  708. value: 'geosite:reddit'
  709. },
  710. {
  711. label: 'Speedtest',
  712. value: 'geosite:speedtest'
  713. },
  714. ]
  715. },
  716. defaultObservatory: {
  717. subjectSelector: [],
  718. probeURL: "https://www.google.com/generate_204",
  719. probeInterval: "1m",
  720. enableConcurrency: true
  721. },
  722. defaultBurstObservatory: {
  723. subjectSelector: [],
  724. pingConfig: {
  725. destination: "https://www.google.com/generate_204",
  726. interval: "1m",
  727. connectivity: "http://connectivitycheck.platform.hicloud.com/generate_204",
  728. timeout: "5s",
  729. sampling: 2
  730. }
  731. }
  732. },
  733. methods: {
  734. loading(spinning = true) {
  735. this.loadingStates.spinning = spinning;
  736. },
  737. async getOutboundsTraffic() {
  738. const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic");
  739. if (msg.success) {
  740. this.outboundsTraffic = msg.obj;
  741. }
  742. },
  743. async getXraySetting() {
  744. const msg = await HttpUtil.post("/panel/xray/");
  745. if (msg.success) {
  746. if (!this.loadingStates.fetched) {
  747. this.loadingStates.fetched = true
  748. }
  749. result = JSON.parse(msg.obj);
  750. xs = JSON.stringify(result.xraySetting, null, 2);
  751. this.oldXraySetting = xs;
  752. this.xraySetting = xs;
  753. this.inboundTags = result.inboundTags;
  754. this.outboundTestUrl = result.outboundTestUrl || 'https://www.google.com/generate_204';
  755. this.oldOutboundTestUrl = this.outboundTestUrl;
  756. this.saveBtnDisable = true;
  757. }
  758. },
  759. async updateXraySetting() {
  760. this.loading(true);
  761. const msg = await HttpUtil.post("/panel/xray/update", {
  762. xraySetting: this.xraySetting,
  763. outboundTestUrl: this.outboundTestUrl || 'https://www.google.com/generate_204'
  764. });
  765. this.loading(false);
  766. if (msg.success) {
  767. await this.getXraySetting();
  768. }
  769. },
  770. async restartXray() {
  771. this.loading(true);
  772. const msg = await HttpUtil.post("/panel/api/server/restartXrayService");
  773. this.loading(false);
  774. if (msg.success) {
  775. await PromiseUtil.sleep(500);
  776. await this.getXrayResult();
  777. }
  778. this.loading(false);
  779. },
  780. async getXrayResult() {
  781. const msg = await HttpUtil.get("/panel/xray/getXrayResult");
  782. if (msg.success) {
  783. this.restartResult = msg.obj;
  784. if (msg.obj.length > 1) Vue.prototype.$message.error(msg.obj);
  785. }
  786. },
  787. async resetXrayConfigToDefault() {
  788. this.loading(true);
  789. const msg = await HttpUtil.get("/panel/setting/getDefaultJsonConfig");
  790. this.loading(false);
  791. if (msg.success) {
  792. this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
  793. this.saveBtnDisable = true;
  794. }
  795. },
  796. changePage(pageKey) {
  797. if (pageKey == 'tpl-advanced') this.changeCode();
  798. if (pageKey == 'tpl-balancer') this.changeObsCode();
  799. },
  800. syncRulesWithOutbound(tag, setting) {
  801. const newTemplateSettings = this.templateSettings;
  802. const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
  803. const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
  804. if (!haveRules && outboundIndex > 0) {
  805. newTemplateSettings.outbounds.splice(outboundIndex);
  806. }
  807. if (haveRules && outboundIndex < 0) {
  808. newTemplateSettings.outbounds.push(setting);
  809. }
  810. this.templateSettings = newTemplateSettings;
  811. },
  812. templateRuleGetter(routeSettings) {
  813. const {
  814. property,
  815. outboundTag
  816. } = routeSettings;
  817. let result = [];
  818. if (this.templateSettings != null) {
  819. this.templateSettings.routing.rules.forEach(
  820. (routingRule) => {
  821. if (
  822. routingRule.hasOwnProperty(property) &&
  823. routingRule.hasOwnProperty("outboundTag") &&
  824. routingRule.outboundTag === outboundTag
  825. ) {
  826. result.push(...routingRule[property]);
  827. }
  828. }
  829. );
  830. }
  831. return result;
  832. },
  833. templateRuleSetter(routeSettings) {
  834. const {
  835. data,
  836. property,
  837. outboundTag
  838. } = routeSettings;
  839. const oldTemplateSettings = this.templateSettings;
  840. const newTemplateSettings = oldTemplateSettings;
  841. currentProperty = this.templateRuleGetter({
  842. outboundTag,
  843. property
  844. })
  845. if (currentProperty.length == 0) {
  846. const propertyRule = {
  847. type: "field",
  848. outboundTag,
  849. [property]: data
  850. };
  851. newTemplateSettings.routing.rules.push(propertyRule);
  852. } else {
  853. const newRules = [];
  854. insertedOnce = false;
  855. newTemplateSettings.routing.rules.forEach(
  856. (routingRule) => {
  857. if (
  858. routingRule.hasOwnProperty(property) &&
  859. routingRule.hasOwnProperty("outboundTag") &&
  860. routingRule.outboundTag === outboundTag
  861. ) {
  862. if (!insertedOnce && data.length > 0) {
  863. insertedOnce = true;
  864. routingRule[property] = data;
  865. newRules.push(routingRule);
  866. }
  867. } else {
  868. newRules.push(routingRule);
  869. }
  870. }
  871. );
  872. newTemplateSettings.routing.rules = newRules;
  873. }
  874. this.templateSettings = newTemplateSettings;
  875. },
  876. changeCode() {
  877. if (this.cm != null) {
  878. this.cm.toTextArea();
  879. }
  880. textAreaObj = document.getElementById('xraySetting');
  881. textAreaObj.value = this[this.advSettings];
  882. this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
  883. this.cm.on('change', editor => {
  884. value = editor.getValue();
  885. if (this.isJsonString(value)) {
  886. this[this.advSettings] = value;
  887. }
  888. });
  889. },
  890. changeObsCode() {
  891. if (this.cm != null) {
  892. this.cm.toTextArea();
  893. }
  894. if (this.obsSettings == '') {
  895. this.cm = null;
  896. return
  897. }
  898. textAreaObj = document.getElementById('obsSetting');
  899. textAreaObj.value = this[this.obsSettings];
  900. this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
  901. this.cm.on('change', editor => {
  902. value = editor.getValue();
  903. if (this.isJsonString(value)) {
  904. this[this.obsSettings] = value;
  905. }
  906. });
  907. },
  908. isJsonString(str) {
  909. try {
  910. JSON.parse(str);
  911. } catch (e) {
  912. return false;
  913. }
  914. return true;
  915. },
  916. findOutboundTraffic(o) {
  917. for (const otraffic of this.outboundsTraffic) {
  918. if (otraffic.tag == o.tag) {
  919. return `↑ ${SizeFormatter.sizeFormat(otraffic.up)} / ${SizeFormatter.sizeFormat(otraffic.down)} ↓`
  920. }
  921. }
  922. return `${SizeFormatter.sizeFormat(0)} / ${SizeFormatter.sizeFormat(0)}`
  923. },
  924. findOutboundAddress(o) {
  925. serverObj = null;
  926. switch (o.protocol) {
  927. case Protocols.VMess:
  928. serverObj = o.settings.vnext;
  929. break;
  930. case Protocols.VLESS:
  931. return [o.settings?.address + ':' + o.settings?.port];
  932. case Protocols.HTTP:
  933. case Protocols.Socks:
  934. case Protocols.Shadowsocks:
  935. case Protocols.Trojan:
  936. serverObj = o.settings.servers;
  937. break;
  938. case Protocols.DNS:
  939. return [o.settings?.address + ':' + o.settings?.port];
  940. case Protocols.Wireguard:
  941. return o.settings.peers.map(peer => peer.endpoint);
  942. default:
  943. return null;
  944. }
  945. return serverObj ? serverObj.map(obj => obj.address + ':' + obj.port) : null;
  946. },
  947. addOutbound() {
  948. outModal.show({
  949. title: '{{ i18n "pages.xray.outbound.addOutbound"}}',
  950. okText: '{{ i18n "pages.xray.outbound.addOutbound" }}',
  951. confirm: (outbound) => {
  952. outModal.loading();
  953. if (outbound.tag.length > 0) {
  954. this.templateSettings.outbounds.push(outbound);
  955. this.outboundSettings = JSON.stringify(this.templateSettings.outbounds);
  956. }
  957. outModal.close();
  958. },
  959. isEdit: false,
  960. tags: this.templateSettings.outbounds.map(obj => obj.tag)
  961. });
  962. },
  963. editOutbound(index) {
  964. outModal.show({
  965. title: '{{ i18n "pages.xray.outbound.editOutbound"}} ' + (index + 1),
  966. outbound: app.templateSettings.outbounds[index],
  967. confirm: (outbound) => {
  968. outModal.loading();
  969. this.templateSettings.outbounds[index] = outbound;
  970. this.outboundSettings = JSON.stringify(this.templateSettings.outbounds);
  971. outModal.close();
  972. },
  973. isEdit: true,
  974. tags: this.outboundData.filter((o) => o.key != index).map(obj => obj.tag)
  975. });
  976. },
  977. deleteOutbound(index) {
  978. outbounds = this.templateSettings.outbounds;
  979. outbounds.splice(index, 1);
  980. this.outboundSettings = JSON.stringify(outbounds);
  981. },
  982. setFirstOutbound(index) {
  983. outbounds = this.templateSettings.outbounds;
  984. outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
  985. this.outboundSettings = JSON.stringify(outbounds);
  986. },
  987. async testOutbound(index) {
  988. const outbound = this.templateSettings.outbounds[index];
  989. if (!outbound) {
  990. Vue.prototype.$message.error('{{ i18n "pages.xray.outbound.testError" }}');
  991. return;
  992. }
  993. if (outbound.protocol === 'blackhole' || outbound.tag === 'blocked') {
  994. Vue.prototype.$message.warning(
  995. '{{ i18n "pages.xray.outbound.testError" }}: blocked/blackhole outbound');
  996. return;
  997. }
  998. // Initialize test state for this outbound if not exists
  999. if (!this.outboundTestStates[index]) {
  1000. this.$set(this.outboundTestStates, index, {
  1001. testing: false,
  1002. result: null
  1003. });
  1004. }
  1005. // Set testing state
  1006. this.$set(this.outboundTestStates[index], 'testing', true);
  1007. this.$set(this.outboundTestStates[index], 'result', null);
  1008. try {
  1009. const outboundJSON = JSON.stringify(outbound);
  1010. const allOutboundsJSON = JSON.stringify(this.templateSettings.outbounds || []);
  1011. const msg = await HttpUtil.post("/panel/xray/testOutbound", {
  1012. outbound: outboundJSON,
  1013. allOutbounds: allOutboundsJSON
  1014. });
  1015. // Update test state
  1016. this.$set(this.outboundTestStates[index], 'testing', false);
  1017. if (msg.success && msg.obj) {
  1018. const result = msg.obj;
  1019. this.$set(this.outboundTestStates[index], 'result', result);
  1020. if (result.success) {
  1021. Vue.prototype.$message.success(
  1022. `{{ i18n "pages.xray.outbound.testSuccess" }}: ${result.delay}ms (${result.statusCode})`
  1023. );
  1024. } else {
  1025. Vue.prototype.$message.error(
  1026. `{{ i18n "pages.xray.outbound.testFailed" }}: ${result.error || 'Unknown error'}`
  1027. );
  1028. }
  1029. } else {
  1030. this.$set(this.outboundTestStates[index], 'result', {
  1031. success: false,
  1032. error: msg.msg || '{{ i18n "pages.xray.outbound.testError" }}'
  1033. });
  1034. Vue.prototype.$message.error(msg.msg || '{{ i18n "pages.xray.outbound.testError" }}');
  1035. }
  1036. } catch (error) {
  1037. this.$set(this.outboundTestStates[index], 'testing', false);
  1038. this.$set(this.outboundTestStates[index], 'result', {
  1039. success: false,
  1040. error: error.message || '{{ i18n "pages.xray.outbound.testError" }}'
  1041. });
  1042. Vue.prototype.$message.error('{{ i18n "pages.xray.outbound.testError" }}: ' + error.message);
  1043. }
  1044. },
  1045. addReverse() {
  1046. reverseModal.show({
  1047. title: '{{ i18n "pages.xray.outbound.addReverse"}}',
  1048. okText: '{{ i18n "pages.xray.outbound.addReverse" }}',
  1049. confirm: (reverse, rules) => {
  1050. reverseModal.loading();
  1051. if (reverse.tag.length > 0) {
  1052. newTemplateSettings = this.templateSettings;
  1053. if (newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {};
  1054. if (newTemplateSettings.reverse[reverse.type + 's'] == undefined) newTemplateSettings.reverse[
  1055. reverse.type + 's'] = [];
  1056. newTemplateSettings.reverse[reverse.type + 's'].push({
  1057. tag: reverse.tag,
  1058. domain: reverse.domain
  1059. });
  1060. this.templateSettings = newTemplateSettings;
  1061. // Add related rules
  1062. this.templateSettings.routing.rules.push(...rules);
  1063. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  1064. }
  1065. reverseModal.close();
  1066. },
  1067. isEdit: false
  1068. });
  1069. },
  1070. editReverse(index) {
  1071. if (this.reverseData[index].type == "bridge") {
  1072. oldRules = this.templateSettings.routing.rules.filter(r => r.inboundTag && r.inboundTag[0] == this
  1073. .reverseData[index].tag);
  1074. } else {
  1075. oldRules = this.templateSettings.routing.rules.filter(r => r.outboundTag && r.outboundTag == this
  1076. .reverseData[index].tag);
  1077. }
  1078. reverseModal.show({
  1079. title: '{{ i18n "pages.xray.outbound.editReverse"}} ' + (index + 1),
  1080. reverse: this.reverseData[index],
  1081. rules: oldRules,
  1082. confirm: (reverse, rules) => {
  1083. reverseModal.loading();
  1084. if (reverse.tag.length > 0) {
  1085. oldData = this.reverseData[index];
  1086. newTemplateSettings = this.templateSettings;
  1087. oldReverseIndex = newTemplateSettings.reverse[oldData.type + 's'].findIndex(rs => rs.tag ==
  1088. oldData.tag);
  1089. oldRuleIndex0 = oldRules.length > 0 ? newTemplateSettings.routing.rules.findIndex(r => JSON
  1090. .stringify(r) == JSON.stringify(oldRules[0])) : -1;
  1091. oldRuleIndex1 = oldRules.length == 2 ? newTemplateSettings.routing.rules.findIndex(r => JSON
  1092. .stringify(r) == JSON.stringify(oldRules[1])) : -1;
  1093. if (oldData.type == reverse.type) {
  1094. newTemplateSettings.reverse[oldData.type + 's'][oldReverseIndex] = {
  1095. tag: reverse.tag,
  1096. domain: reverse.domain
  1097. };
  1098. } else {
  1099. newTemplateSettings.reverse[oldData.type + 's'].splice(oldReverseIndex, 1);
  1100. // delete empty object
  1101. if (newTemplateSettings.reverse[oldData.type + 's'].length == 0) Reflect.deleteProperty(
  1102. newTemplateSettings.reverse, oldData.type + 's');
  1103. // add other type of reverse if it is not exist
  1104. if (!newTemplateSettings.reverse[reverse.type + 's']) newTemplateSettings.reverse[reverse
  1105. .type + 's'] = [];
  1106. newTemplateSettings.reverse[reverse.type + 's'].push({
  1107. tag: reverse.tag,
  1108. domain: reverse.domain
  1109. });
  1110. }
  1111. this.templateSettings = newTemplateSettings;
  1112. // Adjust Rules
  1113. newRules = this.templateSettings.routing.rules;
  1114. oldRuleIndex0 != -1 ? newRules[oldRuleIndex0] = rules[0] : newRules.push(rules[0]);
  1115. oldRuleIndex1 != -1 ? newRules[oldRuleIndex1] = rules[1] : newRules.push(rules[1]);
  1116. this.routingRuleSettings = JSON.stringify(newRules);
  1117. }
  1118. reverseModal.close();
  1119. },
  1120. isEdit: true
  1121. });
  1122. },
  1123. deleteReverse(index) {
  1124. oldData = this.reverseData[index];
  1125. newTemplateSettings = this.templateSettings;
  1126. reverseTypeObj = newTemplateSettings.reverse[oldData.type + 's'];
  1127. realIndex = reverseTypeObj.findIndex(r => r.tag == oldData.tag && r.domain == oldData.domain);
  1128. newTemplateSettings.reverse[oldData.type + 's'].splice(realIndex, 1);
  1129. // delete empty objects
  1130. if (reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type + 's');
  1131. if (Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings,
  1132. 'reverse');
  1133. // delete related routing rules
  1134. newRules = newTemplateSettings.routing.rules;
  1135. if (oldData.type == "bridge") {
  1136. newRules = newTemplateSettings.routing.rules.filter(r => !(r.inboundTag && r.inboundTag.length == 1 && r
  1137. .inboundTag[0] == oldData.tag));
  1138. } else if (oldData.type == "portal") {
  1139. newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
  1140. }
  1141. newTemplateSettings.routing.rules = newRules;
  1142. this.templateSettings = newTemplateSettings;
  1143. },
  1144. async refreshOutboundTraffic() {
  1145. if (!this.refreshing) {
  1146. this.refreshing = true;
  1147. await this.getOutboundsTraffic();
  1148. data = []
  1149. if (this.templateSettings != null) {
  1150. this.templateSettings.outbounds.forEach((o, index) => {
  1151. data.push({
  1152. 'key': index,
  1153. ...o
  1154. });
  1155. });
  1156. }
  1157. this.outboundData = data;
  1158. this.refreshing = false;
  1159. }
  1160. },
  1161. async resetOutboundTraffic(index) {
  1162. let tag = "-alltags-";
  1163. if (index >= 0) {
  1164. tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
  1165. }
  1166. const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", {
  1167. tag: tag
  1168. });
  1169. if (msg.success) {
  1170. await this.refreshOutboundTraffic();
  1171. }
  1172. },
  1173. addBalancer() {
  1174. balancerModal.show({
  1175. title: '{{ i18n "pages.xray.balancer.addBalancer"}}',
  1176. okText: '{{ i18n "pages.xray.balancer.addBalancer"}}',
  1177. balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
  1178. balancer: {
  1179. tag: '',
  1180. strategy: 'random',
  1181. selector: [],
  1182. fallbackTag: ''
  1183. },
  1184. confirm: (balancer) => {
  1185. balancerModal.loading();
  1186. newTemplateSettings = this.templateSettings;
  1187. if (newTemplateSettings.routing.balancers == undefined) {
  1188. newTemplateSettings.routing.balancers = [];
  1189. }
  1190. let tmpBalancer = {
  1191. 'tag': balancer.tag,
  1192. 'selector': balancer.selector,
  1193. 'fallbackTag': balancer.fallbackTag
  1194. };
  1195. if (balancer.strategy && balancer.strategy != 'random') {
  1196. tmpBalancer.strategy = {
  1197. 'type': balancer.strategy
  1198. };
  1199. }
  1200. newTemplateSettings.routing.balancers.push(tmpBalancer);
  1201. this.templateSettings = newTemplateSettings;
  1202. this.updateObservatorySelectors();
  1203. balancerModal.close();
  1204. this.changeObsCode();
  1205. },
  1206. isEdit: false
  1207. });
  1208. },
  1209. editBalancer(index) {
  1210. const oldTag = this.balancersData[index].tag;
  1211. balancerModal.show({
  1212. title: '{{ i18n "pages.xray.balancer.editBalancer"}}',
  1213. okText: '{{ i18n "sure" }}',
  1214. balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
  1215. balancer: this.balancersData[index],
  1216. confirm: (balancer) => {
  1217. balancerModal.loading();
  1218. newTemplateSettings = this.templateSettings;
  1219. let tmpBalancer = {
  1220. 'tag': balancer.tag,
  1221. 'selector': balancer.selector,
  1222. 'fallbackTag': balancer.fallbackTag
  1223. };
  1224. // Remove old tag
  1225. if (newTemplateSettings.observatory) {
  1226. newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory
  1227. .subjectSelector.filter(s => s != oldTag);
  1228. }
  1229. if (newTemplateSettings.burstObservatory) {
  1230. newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory
  1231. .subjectSelector.filter(s => s != oldTag);
  1232. }
  1233. if (balancer.strategy && balancer.strategy != 'random') {
  1234. tmpBalancer.strategy = {
  1235. 'type': balancer.strategy
  1236. };
  1237. }
  1238. newTemplateSettings.routing.balancers[index] = tmpBalancer;
  1239. // change edited tag if used in rule section
  1240. if (oldTag != balancer.tag) {
  1241. newTemplateSettings.routing.rules.forEach((rule) => {
  1242. if (rule.balancerTag && rule.balancerTag == oldTag) {
  1243. rule.balancerTag = balancer.tag;
  1244. }
  1245. });
  1246. }
  1247. this.templateSettings = newTemplateSettings;
  1248. this.updateObservatorySelectors();
  1249. balancerModal.close();
  1250. this.changeObsCode();
  1251. },
  1252. isEdit: true
  1253. });
  1254. },
  1255. updateObservatorySelectors() {
  1256. newTemplateSettings = this.templateSettings;
  1257. const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing');
  1258. const leastLoads = this.balancersData.filter((b) =>
  1259. b.strategy === 'leastLoad' ||
  1260. b.strategy === 'roundRobin' ||
  1261. b.strategy === 'random'
  1262. );
  1263. if (leastPings.length > 0) {
  1264. if (!newTemplateSettings.observatory)
  1265. newTemplateSettings.observatory = this.defaultObservatory;
  1266. newTemplateSettings.observatory.subjectSelector = [];
  1267. leastPings.forEach((b) => {
  1268. b.selector.forEach((s) => {
  1269. if (!newTemplateSettings.observatory.subjectSelector.includes(s))
  1270. newTemplateSettings.observatory.subjectSelector.push(s);
  1271. });
  1272. });
  1273. } else {
  1274. delete newTemplateSettings.observatory
  1275. }
  1276. if (leastLoads.length > 0) {
  1277. if (!newTemplateSettings.burstObservatory)
  1278. newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
  1279. newTemplateSettings.burstObservatory.subjectSelector = [];
  1280. leastLoads.forEach((b) => {
  1281. b.selector.forEach((s) => {
  1282. if (!newTemplateSettings.burstObservatory.subjectSelector.includes(s))
  1283. newTemplateSettings.burstObservatory.subjectSelector.push(s);
  1284. });
  1285. });
  1286. } else {
  1287. delete newTemplateSettings.burstObservatory
  1288. }
  1289. this.templateSettings = newTemplateSettings;
  1290. this.changeObsCode();
  1291. },
  1292. deleteBalancer(index) {
  1293. newTemplateSettings = this.templateSettings;
  1294. // Remove from balancers
  1295. const removedBalancer = this.balancersData.splice(index, 1)[0];
  1296. // Remove from settings
  1297. let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
  1298. newTemplateSettings.routing.balancers.splice(realIndex, 1);
  1299. // Update balancers property to an empty array if there are no more balancers
  1300. if (newTemplateSettings.routing.balancers.length === 0) {
  1301. delete newTemplateSettings.routing.balancers;
  1302. }
  1303. // Remove orphaned balancer references from routing rules
  1304. if (newTemplateSettings.routing.rules) {
  1305. newTemplateSettings.routing.rules.forEach((rule) => {
  1306. if (rule.balancerTag && rule.balancerTag === removedBalancer.tag) {
  1307. delete rule.balancerTag;
  1308. }
  1309. });
  1310. }
  1311. this.templateSettings = newTemplateSettings;
  1312. this.updateObservatorySelectors();
  1313. this.obsSettings = '';
  1314. this.changeObsCode()
  1315. },
  1316. openDNSPresets() {
  1317. dnsPresetsModal.show({
  1318. title: '{{ i18n "pages.xray.dns.dnsPresetTitle" }}',
  1319. selected: (selectedPreset) => {
  1320. this.dnsServers = selectedPreset;
  1321. dnsPresetsModal.close();
  1322. }
  1323. });
  1324. },
  1325. addDNSServer() {
  1326. dnsModal.show({
  1327. title: '{{ i18n "pages.xray.dns.add" }}',
  1328. confirm: (dnsServer) => {
  1329. dnsServers = this.dnsServers;
  1330. dnsServers.push(dnsServer);
  1331. this.dnsServers = dnsServers;
  1332. dnsModal.close();
  1333. },
  1334. isEdit: false
  1335. });
  1336. },
  1337. editDNSServer(index) {
  1338. dnsModal.show({
  1339. title: '{{ i18n "pages.xray.dns.edit" }} #' + (index + 1),
  1340. dnsServer: this.dnsServers[index],
  1341. confirm: (dnsServer) => {
  1342. dnsServers = this.dnsServers;
  1343. dnsServers[index] = dnsServer;
  1344. this.dnsServers = dnsServers;
  1345. dnsModal.close();
  1346. },
  1347. isEdit: true
  1348. });
  1349. },
  1350. deleteDNSServer(index) {
  1351. newDnsServers = this.dnsServers;
  1352. newDnsServers.splice(index, 1);
  1353. this.dnsServers = newDnsServers;
  1354. },
  1355. addFakedns() {
  1356. fakednsModal.show({
  1357. title: '{{ i18n "pages.xray.fakedns.add" }}',
  1358. confirm: (item) => {
  1359. fakeDns = this.fakeDns ?? [];
  1360. fakeDns.push(item);
  1361. this.fakeDns = fakeDns;
  1362. fakednsModal.close();
  1363. },
  1364. isEdit: false
  1365. });
  1366. },
  1367. editFakedns(index) {
  1368. fakednsModal.show({
  1369. title: '{{ i18n "pages.xray.fakedns.edit" }} #' + (index + 1),
  1370. fakeDns: this.fakeDns[index],
  1371. confirm: (item) => {
  1372. fakeDns = this.fakeDns;
  1373. fakeDns[index] = item;
  1374. this.fakeDns = fakeDns;
  1375. fakednsModal.close();
  1376. },
  1377. isEdit: true
  1378. });
  1379. },
  1380. deleteFakedns(index) {
  1381. fakeDns = this.fakeDns;
  1382. fakeDns.splice(index, 1);
  1383. this.fakeDns = fakeDns;
  1384. },
  1385. addRule() {
  1386. ruleModal.show({
  1387. title: '{{ i18n "pages.xray.rules.add"}}',
  1388. okText: '{{ i18n "pages.xray.rules.add" }}',
  1389. confirm: (rule) => {
  1390. ruleModal.loading();
  1391. if (JSON.stringify(rule).length > 3) {
  1392. this.templateSettings.routing.rules.push(rule);
  1393. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  1394. }
  1395. ruleModal.close();
  1396. },
  1397. isEdit: false
  1398. });
  1399. },
  1400. editRule(index) {
  1401. ruleModal.show({
  1402. title: '{{ i18n "pages.xray.rules.edit"}} ' + (index + 1),
  1403. rule: app.templateSettings.routing.rules[index],
  1404. confirm: (rule) => {
  1405. ruleModal.loading();
  1406. if (JSON.stringify(rule).length > 3) {
  1407. this.templateSettings.routing.rules[index] = rule;
  1408. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  1409. }
  1410. ruleModal.close();
  1411. },
  1412. isEdit: true
  1413. });
  1414. },
  1415. replaceRule(old_index, new_index) {
  1416. rules = this.templateSettings.routing.rules;
  1417. if (new_index >= rules.length) rules.push(undefined);
  1418. rules.splice(new_index, 0, rules.splice(old_index, 1)[0]);
  1419. this.routingRuleSettings = JSON.stringify(rules);
  1420. },
  1421. deleteRule(index) {
  1422. rules = this.templateSettings.routing.rules;
  1423. rules.splice(index, 1);
  1424. this.routingRuleSettings = JSON.stringify(rules);
  1425. },
  1426. showWarp() {
  1427. warpModal.show();
  1428. },
  1429. showNord() {
  1430. nordModal.show();
  1431. },
  1432. async loadCustomGeoAliases() {
  1433. try {
  1434. const msg = await HttpUtil.get('/panel/api/custom-geo/aliases');
  1435. if (!msg.success) {
  1436. console.warn('Failed to load custom geo aliases:', msg.msg || 'request failed');
  1437. return;
  1438. }
  1439. if (!msg.obj) return;
  1440. const geoip = msg.obj.geoip ?? [];
  1441. const geosite = msg.obj.geosite ?? [];
  1442. const geoSuffix = this.customGeoAliasLabelSuffix || '';
  1443. geoip.forEach((x) => {
  1444. this.settingsData.IPsOptions.push({
  1445. label: x.alias + geoSuffix,
  1446. value: x.extExample,
  1447. });
  1448. });
  1449. geosite.forEach((x) => {
  1450. const opt = {
  1451. label: x.alias + geoSuffix,
  1452. value: x.extExample
  1453. };
  1454. this.settingsData.DomainsOptions.push(opt);
  1455. this.settingsData.BlockDomainsOptions.push(opt);
  1456. });
  1457. } catch (e) {
  1458. console.error('Failed to load custom geo aliases:', e);
  1459. }
  1460. }
  1461. },
  1462. async mounted() {
  1463. if (window.location.protocol !== "https:") {
  1464. this.showAlert = true;
  1465. }
  1466. await this.getXraySetting();
  1467. await this.loadCustomGeoAliases();
  1468. await this.getXrayResult();
  1469. await this.getOutboundsTraffic();
  1470. if (window.wsClient) {
  1471. window.wsClient.connect();
  1472. window.wsClient.on('outbounds', (payload) => {
  1473. if (payload) {
  1474. this.outboundsTraffic = payload;
  1475. this.$forceUpdate();
  1476. }
  1477. });
  1478. // Handle invalidate signals (sent when payload is too large for WebSocket,
  1479. // or when traffic job notifies about data changes)
  1480. window.wsClient.on('invalidate', (payload) => {
  1481. if (payload && payload.type === 'outbounds') {
  1482. this.refreshOutboundTraffic();
  1483. }
  1484. });
  1485. }
  1486. while (true) {
  1487. await PromiseUtil.sleep(800);
  1488. this.saveBtnDisable = this.oldXraySetting === this.xraySetting && this.oldOutboundTestUrl === this
  1489. .outboundTestUrl;
  1490. }
  1491. },
  1492. computed: {
  1493. templateSettings: {
  1494. get: function() {
  1495. const parsedSettings = this.xraySetting ? JSON.parse(this.xraySetting) : null;
  1496. return parsedSettings;
  1497. },
  1498. set: function(newValue) {
  1499. if (newValue) {
  1500. this.xraySetting = JSON.stringify(newValue, null, 2);
  1501. }
  1502. },
  1503. },
  1504. inboundSettings: {
  1505. get: function() {
  1506. return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null;
  1507. },
  1508. set: function(newValue) {
  1509. newTemplateSettings = this.templateSettings;
  1510. newTemplateSettings.inbounds = JSON.parse(newValue);
  1511. this.templateSettings = newTemplateSettings;
  1512. },
  1513. },
  1514. outboundSettings: {
  1515. get: function() {
  1516. return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null;
  1517. },
  1518. set: function(newValue) {
  1519. newTemplateSettings = this.templateSettings;
  1520. newTemplateSettings.outbounds = JSON.parse(newValue);
  1521. this.templateSettings = newTemplateSettings;
  1522. },
  1523. },
  1524. outboundData: {
  1525. get: function() {
  1526. data = []
  1527. if (this.templateSettings != null) {
  1528. this.templateSettings.outbounds.forEach((o, index) => {
  1529. data.push({
  1530. 'key': index,
  1531. ...o
  1532. });
  1533. });
  1534. }
  1535. return data;
  1536. },
  1537. },
  1538. reverseData: {
  1539. get: function() {
  1540. data = []
  1541. if (this.templateSettings != null && this.templateSettings.reverse != null) {
  1542. if (this.templateSettings.reverse.bridges) {
  1543. this.templateSettings.reverse.bridges.forEach((o, index) => {
  1544. data.push({
  1545. 'key': index,
  1546. 'type': 'bridge',
  1547. ...o
  1548. });
  1549. });
  1550. }
  1551. if (this.templateSettings.reverse.portals) {
  1552. this.templateSettings.reverse.portals.forEach((o, index) => {
  1553. data.push({
  1554. 'key': index,
  1555. 'type': 'portal',
  1556. ...o
  1557. });
  1558. });
  1559. }
  1560. }
  1561. return data;
  1562. },
  1563. },
  1564. routingRuleSettings: {
  1565. get: function() {
  1566. return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null;
  1567. },
  1568. set: function(newValue) {
  1569. newTemplateSettings = this.templateSettings;
  1570. newTemplateSettings.routing.rules = JSON.parse(newValue);
  1571. this.templateSettings = newTemplateSettings;
  1572. },
  1573. },
  1574. routingRuleData: {
  1575. get: function() {
  1576. data = [];
  1577. if (this.templateSettings != null) {
  1578. this.templateSettings.routing.rules.forEach((r, index) => {
  1579. data.push({
  1580. 'key': index,
  1581. ...r
  1582. });
  1583. });
  1584. // Make rules readable
  1585. data.forEach(r => {
  1586. if (r.domain) r.domain = r.domain.join(',')
  1587. if (r.ip) r.ip = r.ip.join(',')
  1588. if (r.source) r.source = r.source.join(',');
  1589. if (r.user) r.user = r.user.join(',')
  1590. if (r.inboundTag) r.inboundTag = r.inboundTag.join(',')
  1591. if (r.protocol) r.protocol = r.protocol.join(',')
  1592. if (r.attrs) r.attrs = JSON.stringify(r.attrs, null, 2)
  1593. });
  1594. }
  1595. return data;
  1596. }
  1597. },
  1598. balancersData: {
  1599. get: function() {
  1600. data = []
  1601. if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings
  1602. .routing.balancers != null) {
  1603. this.templateSettings.routing.balancers.forEach((o, index) => {
  1604. data.push({
  1605. 'key': index,
  1606. 'tag': o.tag ? o.tag : "",
  1607. 'strategy': o.strategy?.type ?? "random",
  1608. 'selector': o.selector ? o.selector : [],
  1609. 'fallbackTag': o.fallbackTag ?? '',
  1610. });
  1611. });
  1612. }
  1613. return data;
  1614. }
  1615. },
  1616. observatory: {
  1617. get: function() {
  1618. return this.templateSettings?.observatory ? JSON.stringify(this.templateSettings.observatory, null, 2) :
  1619. null;
  1620. },
  1621. set: function(newValue) {
  1622. newTemplateSettings = this.templateSettings;
  1623. newTemplateSettings.observatory = JSON.parse(newValue);
  1624. this.templateSettings = newTemplateSettings;
  1625. },
  1626. },
  1627. burstObservatory: {
  1628. get: function() {
  1629. return this.templateSettings?.burstObservatory ? JSON.stringify(this.templateSettings.burstObservatory,
  1630. null, 2) : null;
  1631. },
  1632. set: function(newValue) {
  1633. newTemplateSettings = this.templateSettings;
  1634. newTemplateSettings.burstObservatory = JSON.parse(newValue);
  1635. this.templateSettings = newTemplateSettings;
  1636. },
  1637. },
  1638. observatoryEnable: function() {
  1639. return this.templateSettings != null && this.templateSettings.observatory != undefined
  1640. },
  1641. burstObservatoryEnable: function() {
  1642. return this.templateSettings != null && this.templateSettings.burstObservatory != undefined
  1643. },
  1644. freedomStrategy: {
  1645. get: function() {
  1646. if (!this.templateSettings) return "AsIs";
  1647. freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && o.tag ==
  1648. "direct");
  1649. if (!freedomOutbound) return "AsIs";
  1650. if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
  1651. return freedomOutbound.settings.domainStrategy;
  1652. },
  1653. set: function(newValue) {
  1654. newTemplateSettings = this.templateSettings;
  1655. freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && o
  1656. .tag == "direct");
  1657. if (freedomOutboundIndex == -1) {
  1658. newTemplateSettings.outbounds.push({
  1659. protocol: "freedom",
  1660. tag: "direct",
  1661. settings: {
  1662. "domainStrategy": newValue
  1663. }
  1664. });
  1665. } else if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) {
  1666. newTemplateSettings.outbounds[freedomOutboundIndex].settings = {
  1667. "domainStrategy": newValue
  1668. };
  1669. } else {
  1670. newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue;
  1671. }
  1672. this.templateSettings = newTemplateSettings;
  1673. }
  1674. },
  1675. routingStrategy: {
  1676. get: function() {
  1677. if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing
  1678. .domainStrategy) return "AsIs";
  1679. return this.templateSettings.routing.domainStrategy;
  1680. },
  1681. set: function(newValue) {
  1682. newTemplateSettings = this.templateSettings;
  1683. newTemplateSettings.routing.domainStrategy = newValue;
  1684. this.templateSettings = newTemplateSettings;
  1685. }
  1686. },
  1687. logLevel: {
  1688. get: function() {
  1689. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel)
  1690. return "warning";
  1691. return this.templateSettings.log.loglevel;
  1692. },
  1693. set: function(newValue) {
  1694. newTemplateSettings = this.templateSettings;
  1695. newTemplateSettings.log.loglevel = newValue;
  1696. this.templateSettings = newTemplateSettings;
  1697. }
  1698. },
  1699. accessLog: {
  1700. get: function() {
  1701. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access)
  1702. return "";
  1703. return this.templateSettings.log.access;
  1704. },
  1705. set: function(newValue) {
  1706. newTemplateSettings = this.templateSettings;
  1707. newTemplateSettings.log.access = newValue;
  1708. this.templateSettings = newTemplateSettings;
  1709. }
  1710. },
  1711. errorLog: {
  1712. get: function() {
  1713. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
  1714. return this.templateSettings.log.error;
  1715. },
  1716. set: function(newValue) {
  1717. newTemplateSettings = this.templateSettings;
  1718. newTemplateSettings.log.error = newValue;
  1719. this.templateSettings = newTemplateSettings;
  1720. }
  1721. },
  1722. dnslog: {
  1723. get: function() {
  1724. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.dnsLog)
  1725. return false;
  1726. return this.templateSettings.log.dnsLog;
  1727. },
  1728. set: function(newValue) {
  1729. newTemplateSettings = this.templateSettings;
  1730. newTemplateSettings.log.dnsLog = newValue;
  1731. this.templateSettings = newTemplateSettings;
  1732. }
  1733. },
  1734. statsInboundUplink: {
  1735. get: function() {
  1736. if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy
  1737. .system.statsInboundUplink) return false;
  1738. return this.templateSettings.policy.system.statsInboundUplink;
  1739. },
  1740. set: function(newValue) {
  1741. newTemplateSettings = this.templateSettings;
  1742. newTemplateSettings.policy.system.statsInboundUplink = newValue;
  1743. this.templateSettings = newTemplateSettings;
  1744. }
  1745. },
  1746. statsInboundDownlink: {
  1747. get: function() {
  1748. if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy
  1749. .system.statsInboundDownlink) return false;
  1750. return this.templateSettings.policy.system.statsInboundDownlink;
  1751. },
  1752. set: function(newValue) {
  1753. newTemplateSettings = this.templateSettings;
  1754. newTemplateSettings.policy.system.statsInboundDownlink = newValue;
  1755. this.templateSettings = newTemplateSettings;
  1756. }
  1757. },
  1758. statsOutboundUplink: {
  1759. get: function() {
  1760. if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy
  1761. .system.statsOutboundUplink) return false;
  1762. return this.templateSettings.policy.system.statsOutboundUplink;
  1763. },
  1764. set: function(newValue) {
  1765. newTemplateSettings = this.templateSettings;
  1766. newTemplateSettings.policy.system.statsOutboundUplink = newValue;
  1767. this.templateSettings = newTemplateSettings;
  1768. }
  1769. },
  1770. statsOutboundDownlink: {
  1771. get: function() {
  1772. if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy
  1773. .system.statsOutboundDownlink) return false;
  1774. return this.templateSettings.policy.system.statsOutboundDownlink;
  1775. },
  1776. set: function(newValue) {
  1777. newTemplateSettings = this.templateSettings;
  1778. newTemplateSettings.policy.system.statsOutboundDownlink = newValue;
  1779. this.templateSettings = newTemplateSettings;
  1780. }
  1781. },
  1782. maskAddressLog: {
  1783. get: function() {
  1784. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.maskAddress)
  1785. return "";
  1786. return this.templateSettings.log.maskAddress;
  1787. },
  1788. set: function(newValue) {
  1789. newTemplateSettings = this.templateSettings;
  1790. newTemplateSettings.log.maskAddress = newValue;
  1791. this.templateSettings = newTemplateSettings;
  1792. }
  1793. },
  1794. blockedIPs: {
  1795. get: function() {
  1796. return this.templateRuleGetter({
  1797. outboundTag: "blocked",
  1798. property: "ip"
  1799. });
  1800. },
  1801. set: function(newValue) {
  1802. this.templateRuleSetter({
  1803. outboundTag: "blocked",
  1804. property: "ip",
  1805. data: newValue
  1806. });
  1807. }
  1808. },
  1809. blockedDomains: {
  1810. get: function() {
  1811. return this.templateRuleGetter({
  1812. outboundTag: "blocked",
  1813. property: "domain"
  1814. });
  1815. },
  1816. set: function(newValue) {
  1817. this.templateRuleSetter({
  1818. outboundTag: "blocked",
  1819. property: "domain",
  1820. data: newValue
  1821. });
  1822. }
  1823. },
  1824. blockedProtocols: {
  1825. get: function() {
  1826. return this.templateRuleGetter({
  1827. outboundTag: "blocked",
  1828. property: "protocol"
  1829. });
  1830. },
  1831. set: function(newValue) {
  1832. this.templateRuleSetter({
  1833. outboundTag: "blocked",
  1834. property: "protocol",
  1835. data: newValue
  1836. });
  1837. }
  1838. },
  1839. directIPs: {
  1840. get: function() {
  1841. return this.templateRuleGetter({
  1842. outboundTag: "direct",
  1843. property: "ip"
  1844. });
  1845. },
  1846. set: function(newValue) {
  1847. this.templateRuleSetter({
  1848. outboundTag: "direct",
  1849. property: "ip",
  1850. data: newValue
  1851. });
  1852. this.syncRulesWithOutbound("direct", this.directSettings);
  1853. }
  1854. },
  1855. directDomains: {
  1856. get: function() {
  1857. return this.templateRuleGetter({
  1858. outboundTag: "direct",
  1859. property: "domain"
  1860. });
  1861. },
  1862. set: function(newValue) {
  1863. this.templateRuleSetter({
  1864. outboundTag: "direct",
  1865. property: "domain",
  1866. data: newValue
  1867. });
  1868. this.syncRulesWithOutbound("direct", this.directSettings);
  1869. }
  1870. },
  1871. ipv4Domains: {
  1872. get: function() {
  1873. return this.templateRuleGetter({
  1874. outboundTag: "IPv4",
  1875. property: "domain"
  1876. });
  1877. },
  1878. set: function(newValue) {
  1879. this.templateRuleSetter({
  1880. outboundTag: "IPv4",
  1881. property: "domain",
  1882. data: newValue
  1883. });
  1884. this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
  1885. }
  1886. },
  1887. warpDomains: {
  1888. get: function() {
  1889. return this.templateRuleGetter({
  1890. outboundTag: "warp",
  1891. property: "domain"
  1892. });
  1893. },
  1894. set: function(newValue) {
  1895. this.templateRuleSetter({
  1896. outboundTag: "warp",
  1897. property: "domain",
  1898. data: newValue
  1899. });
  1900. }
  1901. },
  1902. nordTag: {
  1903. get: function() {
  1904. return this.templateSettings ? (this.templateSettings.outbounds.find((o) => o.tag.startsWith(
  1905. "nord-")) || {
  1906. tag: "nord"
  1907. }).tag : "nord";
  1908. }
  1909. },
  1910. nordDomains: {
  1911. get: function() {
  1912. return this.templateRuleGetter({
  1913. outboundTag: this.nordTag,
  1914. property: "domain"
  1915. });
  1916. },
  1917. set: function(newValue) {
  1918. this.templateRuleSetter({
  1919. outboundTag: this.nordTag,
  1920. property: "domain",
  1921. data: newValue
  1922. });
  1923. }
  1924. },
  1925. torrentSettings: {
  1926. get: function() {
  1927. return ArrayUtils.doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
  1928. },
  1929. set: function(newValue) {
  1930. if (newValue) {
  1931. this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent];
  1932. } else {
  1933. this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent
  1934. .includes(data));
  1935. }
  1936. },
  1937. },
  1938. WarpExist: {
  1939. get: function() {
  1940. return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp") >= 0 :
  1941. false;
  1942. },
  1943. },
  1944. NordExist: {
  1945. get: function() {
  1946. return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag.startsWith(
  1947. "nord-")) >= 0 : false;
  1948. },
  1949. },
  1950. enableDNS: {
  1951. get: function() {
  1952. return this.templateSettings ? this.templateSettings.dns != null : false;
  1953. },
  1954. set: function(newValue) {
  1955. newTemplateSettings = this.templateSettings;
  1956. if (newValue) {
  1957. newTemplateSettings.dns = {
  1958. servers: [],
  1959. queryStrategy: "UseIP",
  1960. tag: "dns_inbound",
  1961. enableParallelQuery: false
  1962. };
  1963. newTemplateSettings.fakedns = null;
  1964. } else {
  1965. delete newTemplateSettings.dns;
  1966. delete newTemplateSettings.fakedns;
  1967. }
  1968. this.templateSettings = newTemplateSettings;
  1969. }
  1970. },
  1971. dnsTag: {
  1972. get: function() {
  1973. return this.enableDNS ? this.templateSettings.dns.tag : "";
  1974. },
  1975. set: function(newValue) {
  1976. newTemplateSettings = this.templateSettings;
  1977. newTemplateSettings.dns.tag = newValue;
  1978. this.templateSettings = newTemplateSettings;
  1979. }
  1980. },
  1981. dnsClientIp: {
  1982. get: function() {
  1983. return this.enableDNS ? this.templateSettings.dns.clientIp : null;
  1984. },
  1985. set: function(newValue) {
  1986. newTemplateSettings = this.templateSettings;
  1987. if (newValue) {
  1988. newTemplateSettings.dns.clientIp = newValue;
  1989. } else {
  1990. delete newTemplateSettings.dns.clientIp;
  1991. }
  1992. this.templateSettings = newTemplateSettings;
  1993. }
  1994. },
  1995. dnsDisableCache: {
  1996. get: function() {
  1997. return this.enableDNS ? this.templateSettings.dns.disableCache : false;
  1998. },
  1999. set: function(newValue) {
  2000. newTemplateSettings = this.templateSettings;
  2001. if (newValue) {
  2002. newTemplateSettings.dns.disableCache = newValue;
  2003. } else {
  2004. delete newTemplateSettings.dns.disableCache
  2005. }
  2006. this.templateSettings = newTemplateSettings;
  2007. }
  2008. },
  2009. dnsDisableFallback: {
  2010. get: function() {
  2011. return this.enableDNS ? this.templateSettings.dns.disableFallback : false;
  2012. },
  2013. set: function(newValue) {
  2014. newTemplateSettings = this.templateSettings;
  2015. if (newValue) {
  2016. newTemplateSettings.dns.disableFallback = newValue;
  2017. } else {
  2018. delete newTemplateSettings.dns.disableFallback
  2019. }
  2020. this.templateSettings = newTemplateSettings;
  2021. }
  2022. },
  2023. dnsDisableFallbackIfMatch: {
  2024. get: function() {
  2025. return this.enableDNS ? this.templateSettings.dns.disableFallbackIfMatch : false;
  2026. },
  2027. set: function(newValue) {
  2028. newTemplateSettings = this.templateSettings;
  2029. if (newValue) {
  2030. newTemplateSettings.dns.disableFallbackIfMatch = newValue;
  2031. } else {
  2032. delete newTemplateSettings.dns.disableFallbackIfMatch
  2033. }
  2034. this.templateSettings = newTemplateSettings;
  2035. }
  2036. },
  2037. dnsEnableParallelQuery: {
  2038. get: function() {
  2039. return this.enableDNS ? (this.templateSettings.dns.enableParallelQuery || false) : false;
  2040. },
  2041. set: function(newValue) {
  2042. newTemplateSettings = this.templateSettings;
  2043. if (newValue) {
  2044. newTemplateSettings.dns.enableParallelQuery = newValue;
  2045. } else {
  2046. delete newTemplateSettings.dns.enableParallelQuery
  2047. }
  2048. this.templateSettings = newTemplateSettings;
  2049. }
  2050. },
  2051. dnsUseSystemHosts: {
  2052. get: function() {
  2053. return this.enableDNS ? this.templateSettings.dns.useSystemHosts : false;
  2054. },
  2055. set: function(newValue) {
  2056. newTemplateSettings = this.templateSettings;
  2057. if (newValue) {
  2058. newTemplateSettings.dns.useSystemHosts = newValue;
  2059. } else {
  2060. delete newTemplateSettings.dns.useSystemHosts
  2061. }
  2062. this.templateSettings = newTemplateSettings;
  2063. }
  2064. },
  2065. dnsStrategy: {
  2066. get: function() {
  2067. return this.enableDNS ? this.templateSettings.dns.queryStrategy : null;
  2068. },
  2069. set: function(newValue) {
  2070. newTemplateSettings = this.templateSettings;
  2071. newTemplateSettings.dns.queryStrategy = newValue;
  2072. this.templateSettings = newTemplateSettings;
  2073. }
  2074. },
  2075. dnsServers: {
  2076. get: function() {
  2077. return this.enableDNS ? this.templateSettings.dns.servers : [];
  2078. },
  2079. set: function(newValue) {
  2080. newTemplateSettings = this.templateSettings;
  2081. newTemplateSettings.dns.servers = newValue;
  2082. this.templateSettings = newTemplateSettings;
  2083. }
  2084. },
  2085. fakeDns: {
  2086. get: function() {
  2087. return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : [];
  2088. },
  2089. set: function(newValue) {
  2090. newTemplateSettings = this.templateSettings;
  2091. if (this.enableDNS) {
  2092. newTemplateSettings.fakedns = newValue.length > 0 ? newValue : null;
  2093. } else {
  2094. delete newTemplateSettings.fakedns;
  2095. }
  2096. this.templateSettings = newTemplateSettings;
  2097. }
  2098. }
  2099. },
  2100. });
  2101. </script>
  2102. {{ template "page/body_end" .}}