xray.html 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438
  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"}}'>
  13. <transition name="list" appear>
  14. <a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }" message='{{ i18n "secAlertTitle" }}'
  15. 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. <a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }">
  21. <a-spin tip='{{ i18n "loading" }}'></a-spin>
  22. </a-card>
  23. </a-row>
  24. <a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-else>
  25. <a-col>
  26. <a-card hoverable>
  27. <a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }">
  28. <a-col :xs="24" :sm="10" :style="{ padding: '4px' }">
  29. <a-space direction="horizontal">
  30. <a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">
  31. {{ i18n "pages.xray.save" }}
  32. </a-button>
  33. <a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">
  34. {{ i18n "pages.xray.restart" }}
  35. </a-button>
  36. <a-popover v-if="restartResult" :overlay-class-name="themeSwitcher.currentTheme">
  37. <span slot="title">{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
  38. <template slot="content">
  39. <span :style="{ maxWidth: '400px' }" v-for="line in restartResult.split('\n')">[[ line ]]</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. <script>
  144. const rulesColumns = [
  145. { title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } },
  146. {
  147. title: '{{ i18n "pages.xray.rules.source"}}', children: [
  148. { title: 'IP', dataIndex: "sourceIP", align: 'center', width: 20, ellipsis: true },
  149. { title: '{{ i18n "pages.inbounds.port" }}', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true },
  150. { title: 'VLESS Route', dataIndex: 'vlessRoute', align: 'center', width: 15, ellipsis: true }]
  151. },
  152. {
  153. title: '{{ i18n "pages.inbounds.network"}}', children: [
  154. { title: 'L4', dataIndex: 'network', align: 'center', width: 10 },
  155. { title: '{{ i18n "protocol" }}', dataIndex: 'protocol', align: 'center', width: 15, ellipsis: true },
  156. { title: 'Attrs', dataIndex: 'attrs', align: 'center', width: 10, ellipsis: true }]
  157. },
  158. {
  159. title: '{{ i18n "pages.xray.rules.dest"}}', children: [
  160. { title: 'IP', dataIndex: 'ip', align: 'center', width: 20, ellipsis: true },
  161. { title: '{{ i18n "pages.xray.outbound.domain" }}', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
  162. { title: '{{ i18n "pages.inbounds.port" }}', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]
  163. },
  164. {
  165. title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
  166. { title: '{{ i18n "pages.xray.outbound.tag" }}', dataIndex: 'inboundTag', align: 'center', width: 15, ellipsis: true },
  167. { title: '{{ i18n "pages.inbounds.client" }}', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]
  168. },
  169. { title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 17 },
  170. { title: '{{ i18n "pages.xray.rules.balancer"}}', dataIndex: 'balancerTag', align: 'center', width: 15 },
  171. ];
  172. const rulesMobileColumns = [
  173. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  174. { title: '{{ i18n "pages.xray.rules.inbound"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'inbound' } },
  175. { title: '{{ i18n "pages.xray.rules.outbound"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'outbound' } },
  176. { title: '{{ i18n "pages.xray.rules.info"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'info' } },
  177. ];
  178. const outboundColumns = [
  179. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  180. { title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
  181. { title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
  182. { title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
  183. { title: '{{ i18n "pages.inbounds.traffic" }}', align: 'center', width: 50, scopedSlots: { customRender: 'traffic' } },
  184. ];
  185. const reverseColumns = [
  186. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  187. { title: '{{ i18n "pages.xray.outbound.type"}}', dataIndex: 'type', align: 'center', width: 50 },
  188. { title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
  189. { title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
  190. ];
  191. const balancerColumns = [
  192. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  193. { title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
  194. { title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' } },
  195. { title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' } },
  196. ];
  197. const dnsColumns = [
  198. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  199. { title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
  200. { title: '{{ i18n "pages.xray.dns.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
  201. { title: '{{ i18n "pages.xray.dns.expectIPs"}}', align: 'center', width: 50, scopedSlots: { customRender: 'expectIPs' } },
  202. ];
  203. const fakednsColumns = [
  204. { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
  205. { title: '{{ i18n "pages.xray.fakedns.ipPool"}}', dataIndex: 'ipPool', align: 'center', width: 50 },
  206. { title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', align: 'center', width: 50 },
  207. ];
  208. const app = new Vue({
  209. delimiters: ['[[', ']]'],
  210. mixins: [MediaQueryMixin],
  211. el: '#app',
  212. data: {
  213. themeSwitcher,
  214. isDarkTheme: themeSwitcher.isDarkTheme,
  215. loadingStates: {
  216. fetched: false,
  217. spinning: false
  218. },
  219. oldXraySetting: '',
  220. xraySetting: '',
  221. inboundTags: [],
  222. outboundsTraffic: [],
  223. saveBtnDisable: true,
  224. refreshing: false,
  225. restartResult: '',
  226. showAlert: false,
  227. advSettings: 'xraySetting',
  228. obsSettings: '',
  229. cm: null,
  230. cmOptions: {
  231. lineNumbers: true,
  232. mode: "application/json",
  233. lint: true,
  234. styleActiveLine: true,
  235. matchBrackets: true,
  236. theme: "xq",
  237. autoCloseTags: true,
  238. lineWrapping: true,
  239. indentUnit: 2,
  240. indentWithTabs: true,
  241. smartIndent: true,
  242. tabSize: 2,
  243. lineWiseCopyCut: false,
  244. foldGutter: true,
  245. gutters: [
  246. "CodeMirror-lint-markers",
  247. "CodeMirror-linenumbers",
  248. "CodeMirror-foldgutter",
  249. ],
  250. },
  251. ipv4Settings: {
  252. tag: "IPv4",
  253. protocol: "freedom",
  254. settings: {
  255. domainStrategy: "UseIPv4"
  256. }
  257. },
  258. directSettings: {
  259. tag: "direct",
  260. protocol: "freedom"
  261. },
  262. routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
  263. log: {
  264. loglevel: ["none", "debug", "info", "warning", "error"],
  265. access: ["none", "./access.log"],
  266. error: ["none", "./error.log"],
  267. dnsLog: false,
  268. maskAddress: ["quarter", "half", "full"],
  269. },
  270. settingsData: {
  271. protocols: {
  272. bittorrent: ["bittorrent"],
  273. },
  274. IPsOptions: [
  275. { label: 'Private IPs', value: 'geoip:private' },
  276. { label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
  277. { label: '🇨🇳 China', value: 'geoip:cn' },
  278. { label: '🇷🇺 Russia', value: 'ext:geoip_RU.dat:ru' },
  279. { label: '🇻🇳 Vietnam', value: 'geoip:vn' },
  280. { label: '🇪🇸 Spain', value: 'geoip:es' },
  281. { label: '🇮🇩 Indonesia', value: 'geoip:id' },
  282. { label: '🇺🇦 Ukraine', value: 'geoip:ua' },
  283. { label: '🇹🇷 Türkiye', value: 'geoip:tr' },
  284. { label: '🇧🇷 Brazil', value: 'geoip:br' },
  285. ],
  286. DomainsOptions: [
  287. { label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
  288. { label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
  289. { label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
  290. { label: '🇨🇳 China', value: 'geosite:cn' },
  291. { label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
  292. { label: '🇷🇺 Russia', value: 'ext:geosite_RU.dat:ru-available-only-inside' },
  293. { label: '🇷🇺 .ru', value: 'regexp:.*\\.ru$' },
  294. { label: '🇷🇺 .su', value: 'regexp:.*\\.su$' },
  295. { label: '🇷🇺 .рф', value: 'regexp:.*\\.xn--p1ai$' },
  296. { label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
  297. ],
  298. BlockDomainsOptions: [
  299. { label: 'Ads All', value: 'geosite:category-ads-all' },
  300. { label: 'Ads IR 🇮🇷', value: 'ext:geosite_IR.dat:category-ads-all' },
  301. { label: 'Ads RU 🇷🇺', value: 'ext:geosite_RU.dat:category-ads-all' },
  302. { label: 'Malware 🇮🇷', value: 'ext:geosite_IR.dat:malware' },
  303. { label: 'Phishing 🇮🇷', value: 'ext:geosite_IR.dat:phishing' },
  304. { label: 'Cryptominers 🇮🇷', value: 'ext:geosite_IR.dat:cryptominers' },
  305. { label: 'Adult +18', value: 'geosite:category-porn' },
  306. { label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
  307. { label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
  308. { label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
  309. { label: '🇨🇳 China', value: 'geosite:cn' },
  310. { label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
  311. { label: '🇷🇺 Russia', value: 'ext:geosite_RU.dat:ru-available-only-inside' },
  312. { label: '🇷🇺 .ru', value: 'regexp:.*\\.ru$' },
  313. { label: '🇷🇺 .su', value: 'regexp:.*\\.su$' },
  314. { label: '🇷🇺 .рф', value: 'regexp:.*\\.xn--p1ai$' },
  315. { label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
  316. ],
  317. ServicesOptions: [
  318. { label: 'Apple', value: 'geosite:apple' },
  319. { label: 'Meta', value: 'geosite:meta' },
  320. { label: 'Google', value: 'geosite:google' },
  321. { label: 'OpenAI', value: 'geosite:openai' },
  322. { label: 'Spotify', value: 'geosite:spotify' },
  323. { label: 'Netflix', value: 'geosite:netflix' },
  324. { label: 'Reddit', value: 'geosite:reddit' },
  325. { label: 'Speedtest', value: 'geosite:speedtest' },
  326. ]
  327. },
  328. defaultObservatory: {
  329. subjectSelector: [],
  330. probeURL: "http://www.google.com/gen_204",
  331. probeInterval: "10m",
  332. enableConcurrency: true
  333. },
  334. defaultBurstObservatory: {
  335. subjectSelector: [],
  336. pingConfig: {
  337. destination: "http://www.google.com/gen_204",
  338. interval: "30m",
  339. connectivity: "http://connectivitycheck.platform.hicloud.com/generate_204",
  340. timeout: "10s",
  341. sampling: 2
  342. }
  343. }
  344. },
  345. methods: {
  346. loading(spinning = true) {
  347. this.loadingStates.spinning = spinning;
  348. },
  349. async getOutboundsTraffic() {
  350. const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic");
  351. if (msg.success) {
  352. this.outboundsTraffic = msg.obj;
  353. }
  354. },
  355. async getXraySetting() {
  356. const msg = await HttpUtil.post("/panel/xray/");
  357. if (msg.success) {
  358. if (!this.loadingStates.fetched) {
  359. this.loadingStates.fetched = true
  360. }
  361. result = JSON.parse(msg.obj);
  362. xs = JSON.stringify(result.xraySetting, null, 2);
  363. this.oldXraySetting = xs;
  364. this.xraySetting = xs;
  365. this.inboundTags = result.inboundTags;
  366. this.saveBtnDisable = true;
  367. }
  368. },
  369. async updateXraySetting() {
  370. this.loading(true);
  371. const msg = await HttpUtil.post("/panel/xray/update", { xraySetting: this.xraySetting });
  372. this.loading(false);
  373. if (msg.success) {
  374. await this.getXraySetting();
  375. }
  376. },
  377. async restartXray() {
  378. this.loading(true);
  379. const msg = await HttpUtil.post("/panel/api/server/restartXrayService");
  380. this.loading(false);
  381. if (msg.success) {
  382. await PromiseUtil.sleep(500);
  383. await this.getXrayResult();
  384. }
  385. this.loading(false);
  386. },
  387. async getXrayResult() {
  388. const msg = await HttpUtil.get("/panel/xray/getXrayResult");
  389. if (msg.success) {
  390. this.restartResult = msg.obj;
  391. if (msg.obj.length > 1) Vue.prototype.$message.error(msg.obj);
  392. }
  393. },
  394. async resetXrayConfigToDefault() {
  395. this.loading(true);
  396. const msg = await HttpUtil.get("/panel/setting/getDefaultJsonConfig");
  397. this.loading(false);
  398. if (msg.success) {
  399. this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
  400. this.saveBtnDisable = true;
  401. }
  402. },
  403. changePage(pageKey) {
  404. if (pageKey == 'tpl-advanced') this.changeCode();
  405. if (pageKey == 'tpl-balancer') this.changeObsCode();
  406. },
  407. syncRulesWithOutbound(tag, setting) {
  408. const newTemplateSettings = this.templateSettings;
  409. const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
  410. const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
  411. if (!haveRules && outboundIndex > 0) {
  412. newTemplateSettings.outbounds.splice(outboundIndex);
  413. }
  414. if (haveRules && outboundIndex < 0) {
  415. newTemplateSettings.outbounds.push(setting);
  416. }
  417. this.templateSettings = newTemplateSettings;
  418. },
  419. templateRuleGetter(routeSettings) {
  420. const { property, outboundTag } = routeSettings;
  421. let result = [];
  422. if (this.templateSettings != null) {
  423. this.templateSettings.routing.rules.forEach(
  424. (routingRule) => {
  425. if (
  426. routingRule.hasOwnProperty(property) &&
  427. routingRule.hasOwnProperty("outboundTag") &&
  428. routingRule.outboundTag === outboundTag
  429. ) {
  430. result.push(...routingRule[property]);
  431. }
  432. }
  433. );
  434. }
  435. return result;
  436. },
  437. templateRuleSetter(routeSettings) {
  438. const { data, property, outboundTag } = routeSettings;
  439. const oldTemplateSettings = this.templateSettings;
  440. const newTemplateSettings = oldTemplateSettings;
  441. currentProperty = this.templateRuleGetter({ outboundTag, property })
  442. if (currentProperty.length == 0) {
  443. const propertyRule = {
  444. type: "field",
  445. outboundTag,
  446. [property]: data
  447. };
  448. newTemplateSettings.routing.rules.push(propertyRule);
  449. }
  450. else {
  451. const newRules = [];
  452. insertedOnce = false;
  453. newTemplateSettings.routing.rules.forEach(
  454. (routingRule) => {
  455. if (
  456. routingRule.hasOwnProperty(property) &&
  457. routingRule.hasOwnProperty("outboundTag") &&
  458. routingRule.outboundTag === outboundTag
  459. ) {
  460. if (!insertedOnce && data.length > 0) {
  461. insertedOnce = true;
  462. routingRule[property] = data;
  463. newRules.push(routingRule);
  464. }
  465. }
  466. else {
  467. newRules.push(routingRule);
  468. }
  469. }
  470. );
  471. newTemplateSettings.routing.rules = newRules;
  472. }
  473. this.templateSettings = newTemplateSettings;
  474. },
  475. changeCode() {
  476. if (this.cm != null) {
  477. this.cm.toTextArea();
  478. }
  479. textAreaObj = document.getElementById('xraySetting');
  480. textAreaObj.value = this[this.advSettings];
  481. this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
  482. this.cm.on('change', editor => {
  483. value = editor.getValue();
  484. if (this.isJsonString(value)) {
  485. this[this.advSettings] = value;
  486. }
  487. });
  488. },
  489. changeObsCode() {
  490. if (this.cm != null) {
  491. this.cm.toTextArea();
  492. }
  493. if (this.obsSettings == '') {
  494. this.cm = null;
  495. return
  496. }
  497. textAreaObj = document.getElementById('obsSetting');
  498. textAreaObj.value = this[this.obsSettings];
  499. this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
  500. this.cm.on('change', editor => {
  501. value = editor.getValue();
  502. if (this.isJsonString(value)) {
  503. this[this.obsSettings] = value;
  504. }
  505. });
  506. },
  507. isJsonString(str) {
  508. try {
  509. JSON.parse(str);
  510. } catch (e) {
  511. return false;
  512. }
  513. return true;
  514. },
  515. findOutboundTraffic(o) {
  516. for (const otraffic of this.outboundsTraffic) {
  517. if (otraffic.tag == o.tag) {
  518. return SizeFormatter.sizeFormat(otraffic.up) + ' / ' + SizeFormatter.sizeFormat(otraffic.down);
  519. }
  520. }
  521. return SizeFormatter.sizeFormat(0) + ' / ' + SizeFormatter.sizeFormat(0);
  522. },
  523. findOutboundAddress(o) {
  524. serverObj = null;
  525. switch (o.protocol) {
  526. case Protocols.VMess:
  527. case Protocols.VLESS:
  528. serverObj = o.settings.vnext;
  529. break;
  530. case Protocols.HTTP:
  531. case Protocols.Mixed:
  532. case Protocols.Shadowsocks:
  533. case Protocols.Trojan:
  534. serverObj = o.settings.servers;
  535. break;
  536. case Protocols.DNS:
  537. return [o.settings?.address + ':' + o.settings?.port];
  538. case Protocols.Wireguard:
  539. return o.settings.peers.map(peer => peer.endpoint);
  540. default:
  541. return null;
  542. }
  543. return serverObj ? serverObj.map(obj => obj.address + ':' + obj.port) : null;
  544. },
  545. addOutbound() {
  546. outModal.show({
  547. title: '{{ i18n "pages.xray.outbound.addOutbound"}}',
  548. okText: '{{ i18n "pages.xray.outbound.addOutbound" }}',
  549. confirm: (outbound) => {
  550. outModal.loading();
  551. if (outbound.tag.length > 0) {
  552. this.templateSettings.outbounds.push(outbound);
  553. this.outboundSettings = JSON.stringify(this.templateSettings.outbounds);
  554. }
  555. outModal.close();
  556. },
  557. isEdit: false,
  558. tags: this.templateSettings.outbounds.map(obj => obj.tag)
  559. });
  560. },
  561. editOutbound(index) {
  562. outModal.show({
  563. title: '{{ i18n "pages.xray.outbound.editOutbound"}} ' + (index + 1),
  564. outbound: app.templateSettings.outbounds[index],
  565. confirm: (outbound) => {
  566. outModal.loading();
  567. this.templateSettings.outbounds[index] = outbound;
  568. this.outboundSettings = JSON.stringify(this.templateSettings.outbounds);
  569. outModal.close();
  570. },
  571. isEdit: true,
  572. tags: this.outboundData.filter((o) => o.key != index).map(obj => obj.tag)
  573. });
  574. },
  575. deleteOutbound(index) {
  576. outbounds = this.templateSettings.outbounds;
  577. outbounds.splice(index, 1);
  578. this.outboundSettings = JSON.stringify(outbounds);
  579. },
  580. setFirstOutbound(index) {
  581. outbounds = this.templateSettings.outbounds;
  582. outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
  583. this.outboundSettings = JSON.stringify(outbounds);
  584. },
  585. addReverse() {
  586. reverseModal.show({
  587. title: '{{ i18n "pages.xray.outbound.addReverse"}}',
  588. okText: '{{ i18n "pages.xray.outbound.addReverse" }}',
  589. confirm: (reverse, rules) => {
  590. reverseModal.loading();
  591. if (reverse.tag.length > 0) {
  592. newTemplateSettings = this.templateSettings;
  593. if (newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {};
  594. if (newTemplateSettings.reverse[reverse.type + 's'] == undefined) newTemplateSettings.reverse[reverse.type + 's'] = [];
  595. newTemplateSettings.reverse[reverse.type + 's'].push({ tag: reverse.tag, domain: reverse.domain });
  596. this.templateSettings = newTemplateSettings;
  597. // Add related rules
  598. this.templateSettings.routing.rules.push(...rules);
  599. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  600. }
  601. reverseModal.close();
  602. },
  603. isEdit: false
  604. });
  605. },
  606. editReverse(index) {
  607. if (this.reverseData[index].type == "bridge") {
  608. oldRules = this.templateSettings.routing.rules.filter(r => r.inboundTag && r.inboundTag[0] == this.reverseData[index].tag);
  609. } else {
  610. oldRules = this.templateSettings.routing.rules.filter(r => r.outboundTag && r.outboundTag == this.reverseData[index].tag);
  611. }
  612. reverseModal.show({
  613. title: '{{ i18n "pages.xray.outbound.editReverse"}} ' + (index + 1),
  614. reverse: this.reverseData[index],
  615. rules: oldRules,
  616. confirm: (reverse, rules) => {
  617. reverseModal.loading();
  618. if (reverse.tag.length > 0) {
  619. oldData = this.reverseData[index];
  620. newTemplateSettings = this.templateSettings;
  621. oldReverseIndex = newTemplateSettings.reverse[oldData.type + 's'].findIndex(rs => rs.tag == oldData.tag);
  622. oldRuleIndex0 = oldRules.length > 0 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[0])) : -1;
  623. oldRuleIndex1 = oldRules.length == 2 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[1])) : -1;
  624. if (oldData.type == reverse.type) {
  625. newTemplateSettings.reverse[oldData.type + 's'][oldReverseIndex] = { tag: reverse.tag, domain: reverse.domain };
  626. } else {
  627. newTemplateSettings.reverse[oldData.type + 's'].splice(oldReverseIndex, 1);
  628. // delete empty object
  629. if (newTemplateSettings.reverse[oldData.type + 's'].length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type + 's');
  630. // add other type of reverse if it is not exist
  631. if (!newTemplateSettings.reverse[reverse.type + 's']) newTemplateSettings.reverse[reverse.type + 's'] = [];
  632. newTemplateSettings.reverse[reverse.type + 's'].push({ tag: reverse.tag, domain: reverse.domain });
  633. }
  634. this.templateSettings = newTemplateSettings;
  635. // Adjust Rules
  636. newRules = this.templateSettings.routing.rules;
  637. oldRuleIndex0 != -1 ? newRules[oldRuleIndex0] = rules[0] : newRules.push(rules[0]);
  638. oldRuleIndex1 != -1 ? newRules[oldRuleIndex1] = rules[1] : newRules.push(rules[1]);
  639. this.routingRuleSettings = JSON.stringify(newRules);
  640. }
  641. reverseModal.close();
  642. },
  643. isEdit: true
  644. });
  645. },
  646. deleteReverse(index) {
  647. oldData = this.reverseData[index];
  648. newTemplateSettings = this.templateSettings;
  649. reverseTypeObj = newTemplateSettings.reverse[oldData.type + 's'];
  650. realIndex = reverseTypeObj.findIndex(r => r.tag == oldData.tag && r.domain == oldData.domain);
  651. newTemplateSettings.reverse[oldData.type + 's'].splice(realIndex, 1);
  652. // delete empty objects
  653. if (reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type + 's');
  654. if (Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings, 'reverse');
  655. // delete related routing rules
  656. newRules = newTemplateSettings.routing.rules;
  657. if (oldData.type == "bridge") {
  658. newRules = newTemplateSettings.routing.rules.filter(r => !(r.inboundTag && r.inboundTag.length == 1 && r.inboundTag[0] == oldData.tag));
  659. } else if (oldData.type == "portal") {
  660. newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
  661. }
  662. newTemplateSettings.routing.rules = newRules;
  663. this.templateSettings = newTemplateSettings;
  664. },
  665. async refreshOutboundTraffic() {
  666. if (!this.refreshing) {
  667. this.refreshing = true;
  668. await this.getOutboundsTraffic();
  669. data = []
  670. if (this.templateSettings != null) {
  671. this.templateSettings.outbounds.forEach((o, index) => {
  672. data.push({ 'key': index, ...o });
  673. });
  674. }
  675. this.outboundData = data;
  676. this.refreshing = false;
  677. }
  678. },
  679. async resetOutboundTraffic(index) {
  680. let tag = "-alltags-";
  681. if (index >= 0) {
  682. tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
  683. }
  684. const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", { tag: tag });
  685. if (msg.success) {
  686. await this.refreshOutboundTraffic();
  687. }
  688. },
  689. addBalancer() {
  690. balancerModal.show({
  691. title: '{{ i18n "pages.xray.balancer.addBalancer"}}',
  692. okText: '{{ i18n "pages.xray.balancer.addBalancer"}}',
  693. balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
  694. balancer: {
  695. tag: '',
  696. strategy: 'random',
  697. selector: [],
  698. fallbackTag: ''
  699. },
  700. confirm: (balancer) => {
  701. balancerModal.loading();
  702. newTemplateSettings = this.templateSettings;
  703. if (newTemplateSettings.routing.balancers == undefined) {
  704. newTemplateSettings.routing.balancers = [];
  705. }
  706. let tmpBalancer = {
  707. 'tag': balancer.tag,
  708. 'selector': balancer.selector,
  709. 'fallbackTag': balancer.fallbackTag
  710. };
  711. if (balancer.strategy && balancer.strategy != 'random') {
  712. tmpBalancer.strategy = {
  713. 'type': balancer.strategy
  714. };
  715. }
  716. newTemplateSettings.routing.balancers.push(tmpBalancer);
  717. this.templateSettings = newTemplateSettings;
  718. this.updateObservatorySelectors();
  719. balancerModal.close();
  720. this.changeObsCode();
  721. },
  722. isEdit: false
  723. });
  724. },
  725. editBalancer(index) {
  726. const oldTag = this.balancersData[index].tag;
  727. balancerModal.show({
  728. title: '{{ i18n "pages.xray.balancer.editBalancer"}}',
  729. okText: '{{ i18n "sure" }}',
  730. balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
  731. balancer: this.balancersData[index],
  732. confirm: (balancer) => {
  733. balancerModal.loading();
  734. newTemplateSettings = this.templateSettings;
  735. let tmpBalancer = {
  736. 'tag': balancer.tag,
  737. 'selector': balancer.selector,
  738. 'fallbackTag': balancer.fallbackTag
  739. };
  740. // Remove old tag
  741. if (newTemplateSettings.observatory) {
  742. newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != oldTag);
  743. }
  744. if (newTemplateSettings.burstObservatory) {
  745. newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != oldTag);
  746. }
  747. if (balancer.strategy && balancer.strategy != 'random') {
  748. tmpBalancer.strategy = {
  749. 'type': balancer.strategy
  750. };
  751. }
  752. newTemplateSettings.routing.balancers[index] = tmpBalancer;
  753. // change edited tag if used in rule section
  754. if (oldTag != balancer.tag) {
  755. newTemplateSettings.routing.rules.forEach((rule) => {
  756. if (rule.balancerTag && rule.balancerTag == oldTag) {
  757. rule.balancerTag = balancer.tag;
  758. }
  759. });
  760. }
  761. this.templateSettings = newTemplateSettings;
  762. this.updateObservatorySelectors();
  763. balancerModal.close();
  764. this.changeObsCode();
  765. },
  766. isEdit: true
  767. });
  768. },
  769. updateObservatorySelectors() {
  770. newTemplateSettings = this.templateSettings;
  771. const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing');
  772. const leastLoads = this.balancersData.filter((b) =>
  773. b.strategy === 'leastLoad' ||
  774. b.strategy === 'roundRobin' ||
  775. b.strategy === 'random'
  776. );
  777. if (leastPings.length > 0) {
  778. if (!newTemplateSettings.observatory)
  779. newTemplateSettings.observatory = this.defaultObservatory;
  780. newTemplateSettings.observatory.subjectSelector = [];
  781. leastPings.forEach((b) => {
  782. b.selector.forEach((s) => {
  783. if (!newTemplateSettings.observatory.subjectSelector.includes(s))
  784. newTemplateSettings.observatory.subjectSelector.push(s);
  785. });
  786. });
  787. } else {
  788. delete newTemplateSettings.observatory
  789. }
  790. if (leastLoads.length > 0) {
  791. if (!newTemplateSettings.burstObservatory)
  792. newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
  793. newTemplateSettings.burstObservatory.subjectSelector = [];
  794. leastLoads.forEach((b) => {
  795. b.selector.forEach((s) => {
  796. if (!newTemplateSettings.burstObservatory.subjectSelector.includes(s))
  797. newTemplateSettings.burstObservatory.subjectSelector.push(s);
  798. });
  799. });
  800. } else {
  801. delete newTemplateSettings.burstObservatory
  802. }
  803. this.templateSettings = newTemplateSettings;
  804. this.changeObsCode();
  805. },
  806. deleteBalancer(index) {
  807. newTemplateSettings = this.templateSettings;
  808. // Remove from balancers
  809. const removedBalancer = this.balancersData.splice(index, 1)[0];
  810. // Remove from settings
  811. let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
  812. newTemplateSettings.routing.balancers.splice(realIndex, 1);
  813. // Update balancers property to an empty array if there are no more balancers
  814. if (newTemplateSettings.routing.balancers.length === 0) {
  815. delete newTemplateSettings.routing.balancers;
  816. }
  817. this.templateSettings = newTemplateSettings;
  818. this.updateObservatorySelectors();
  819. this.obsSettings = '';
  820. this.changeObsCode()
  821. },
  822. openDNSPresets() {
  823. dnsPresetsModal.show({
  824. title: '{{ i18n "pages.xray.dns.dnsPresetTitle" }}',
  825. selected: (selectedPreset) => {
  826. this.dnsServers = selectedPreset;
  827. dnsPresetsModal.close();
  828. }
  829. });
  830. },
  831. addDNSServer() {
  832. dnsModal.show({
  833. title: '{{ i18n "pages.xray.dns.add" }}',
  834. confirm: (dnsServer) => {
  835. dnsServers = this.dnsServers;
  836. dnsServers.push(dnsServer);
  837. this.dnsServers = dnsServers;
  838. dnsModal.close();
  839. },
  840. isEdit: false
  841. });
  842. },
  843. editDNSServer(index) {
  844. dnsModal.show({
  845. title: '{{ i18n "pages.xray.dns.edit" }} #' + (index + 1),
  846. dnsServer: this.dnsServers[index],
  847. confirm: (dnsServer) => {
  848. dnsServers = this.dnsServers;
  849. dnsServers[index] = dnsServer;
  850. this.dnsServers = dnsServers;
  851. dnsModal.close();
  852. },
  853. isEdit: true
  854. });
  855. },
  856. deleteDNSServer(index) {
  857. newDnsServers = this.dnsServers;
  858. newDnsServers.splice(index, 1);
  859. this.dnsServers = newDnsServers;
  860. },
  861. addFakedns() {
  862. fakednsModal.show({
  863. title: '{{ i18n "pages.xray.fakedns.add" }}',
  864. confirm: (item) => {
  865. fakeDns = this.fakeDns ?? [];
  866. fakeDns.push(item);
  867. this.fakeDns = fakeDns;
  868. fakednsModal.close();
  869. },
  870. isEdit: false
  871. });
  872. },
  873. editFakedns(index) {
  874. fakednsModal.show({
  875. title: '{{ i18n "pages.xray.fakedns.edit" }} #' + (index + 1),
  876. fakeDns: this.fakeDns[index],
  877. confirm: (item) => {
  878. fakeDns = this.fakeDns;
  879. fakeDns[index] = item;
  880. this.fakeDns = fakeDns;
  881. fakednsModal.close();
  882. },
  883. isEdit: true
  884. });
  885. },
  886. deleteFakedns(index) {
  887. fakeDns = this.fakeDns;
  888. fakeDns.splice(index, 1);
  889. this.fakeDns = fakeDns;
  890. },
  891. addRule() {
  892. ruleModal.show({
  893. title: '{{ i18n "pages.xray.rules.add"}}',
  894. okText: '{{ i18n "pages.xray.rules.add" }}',
  895. confirm: (rule) => {
  896. ruleModal.loading();
  897. if (JSON.stringify(rule).length > 3) {
  898. this.templateSettings.routing.rules.push(rule);
  899. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  900. }
  901. ruleModal.close();
  902. },
  903. isEdit: false
  904. });
  905. },
  906. editRule(index) {
  907. ruleModal.show({
  908. title: '{{ i18n "pages.xray.rules.edit"}} ' + (index + 1),
  909. rule: app.templateSettings.routing.rules[index],
  910. confirm: (rule) => {
  911. ruleModal.loading();
  912. if (JSON.stringify(rule).length > 3) {
  913. this.templateSettings.routing.rules[index] = rule;
  914. this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
  915. }
  916. ruleModal.close();
  917. },
  918. isEdit: true
  919. });
  920. },
  921. replaceRule(old_index, new_index) {
  922. rules = this.templateSettings.routing.rules;
  923. if (new_index >= rules.length) rules.push(undefined);
  924. rules.splice(new_index, 0, rules.splice(old_index, 1)[0]);
  925. this.routingRuleSettings = JSON.stringify(rules);
  926. },
  927. deleteRule(index) {
  928. rules = this.templateSettings.routing.rules;
  929. rules.splice(index, 1);
  930. this.routingRuleSettings = JSON.stringify(rules);
  931. },
  932. showWarp() {
  933. warpModal.show();
  934. }
  935. },
  936. async mounted() {
  937. if (window.location.protocol !== "https:") {
  938. this.showAlert = true;
  939. }
  940. await this.getXraySetting();
  941. await this.getXrayResult();
  942. await this.getOutboundsTraffic();
  943. while (true) {
  944. await PromiseUtil.sleep(800);
  945. this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
  946. }
  947. },
  948. computed: {
  949. templateSettings: {
  950. get: function () {
  951. const parsedSettings = this.xraySetting ? JSON.parse(this.xraySetting) : null;
  952. return parsedSettings;
  953. },
  954. set: function (newValue) {
  955. if (newValue) {
  956. this.xraySetting = JSON.stringify(newValue, null, 2);
  957. }
  958. },
  959. },
  960. inboundSettings: {
  961. get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
  962. set: function (newValue) {
  963. newTemplateSettings = this.templateSettings;
  964. newTemplateSettings.inbounds = JSON.parse(newValue);
  965. this.templateSettings = newTemplateSettings;
  966. },
  967. },
  968. outboundSettings: {
  969. get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
  970. set: function (newValue) {
  971. newTemplateSettings = this.templateSettings;
  972. newTemplateSettings.outbounds = JSON.parse(newValue);
  973. this.templateSettings = newTemplateSettings;
  974. },
  975. },
  976. outboundData: {
  977. get: function () {
  978. data = []
  979. if (this.templateSettings != null) {
  980. this.templateSettings.outbounds.forEach((o, index) => {
  981. data.push({ 'key': index, ...o });
  982. });
  983. }
  984. return data;
  985. },
  986. },
  987. reverseData: {
  988. get: function () {
  989. data = []
  990. if (this.templateSettings != null && this.templateSettings.reverse != null) {
  991. if (this.templateSettings.reverse.bridges) {
  992. this.templateSettings.reverse.bridges.forEach((o, index) => {
  993. data.push({ 'key': index, 'type': 'bridge', ...o });
  994. });
  995. }
  996. if (this.templateSettings.reverse.portals) {
  997. this.templateSettings.reverse.portals.forEach((o, index) => {
  998. data.push({ 'key': index, 'type': 'portal', ...o });
  999. });
  1000. }
  1001. }
  1002. return data;
  1003. },
  1004. },
  1005. routingRuleSettings: {
  1006. get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
  1007. set: function (newValue) {
  1008. newTemplateSettings = this.templateSettings;
  1009. newTemplateSettings.routing.rules = JSON.parse(newValue);
  1010. this.templateSettings = newTemplateSettings;
  1011. },
  1012. },
  1013. routingRuleData: {
  1014. get: function () {
  1015. data = [];
  1016. if (this.templateSettings != null) {
  1017. this.templateSettings.routing.rules.forEach((r, index) => {
  1018. data.push({ 'key': index, ...r });
  1019. });
  1020. // Make rules readable
  1021. data.forEach(r => {
  1022. if (r.domain) r.domain = r.domain.join(',')
  1023. if (r.ip) r.ip = r.ip.join(',')
  1024. if (r.source) r.source = r.source.join(',');
  1025. if (r.user) r.user = r.user.join(',')
  1026. if (r.inboundTag) r.inboundTag = r.inboundTag.join(',')
  1027. if (r.protocol) r.protocol = r.protocol.join(',')
  1028. if (r.attrs) r.attrs = JSON.stringify(r.attrs, null, 2)
  1029. });
  1030. }
  1031. return data;
  1032. }
  1033. },
  1034. balancersData: {
  1035. get: function () {
  1036. data = []
  1037. if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
  1038. this.templateSettings.routing.balancers.forEach((o, index) => {
  1039. data.push({
  1040. 'key': index,
  1041. 'tag': o.tag ? o.tag : "",
  1042. 'strategy': o.strategy?.type ?? "random",
  1043. 'selector': o.selector ? o.selector : [],
  1044. 'fallbackTag': o.fallbackTag ?? '',
  1045. });
  1046. });
  1047. }
  1048. return data;
  1049. }
  1050. },
  1051. observatory: {
  1052. get: function () {
  1053. return this.templateSettings?.observatory ? JSON.stringify(this.templateSettings.observatory, null, 2) : null;
  1054. },
  1055. set: function (newValue) {
  1056. newTemplateSettings = this.templateSettings;
  1057. newTemplateSettings.observatory = JSON.parse(newValue);
  1058. this.templateSettings = newTemplateSettings;
  1059. },
  1060. },
  1061. burstObservatory: {
  1062. get: function () {
  1063. return this.templateSettings?.burstObservatory ? JSON.stringify(this.templateSettings.burstObservatory, null, 2) : null;
  1064. },
  1065. set: function (newValue) {
  1066. newTemplateSettings = this.templateSettings;
  1067. newTemplateSettings.burstObservatory = JSON.parse(newValue);
  1068. this.templateSettings = newTemplateSettings;
  1069. },
  1070. },
  1071. observatoryEnable: function () { return this.templateSettings != null && this.templateSettings.observatory != undefined },
  1072. burstObservatoryEnable: function () { return this.templateSettings != null && this.templateSettings.burstObservatory != undefined },
  1073. freedomStrategy: {
  1074. get: function () {
  1075. if (!this.templateSettings) return "AsIs";
  1076. freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && o.tag == "direct");
  1077. if (!freedomOutbound) return "AsIs";
  1078. if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
  1079. return freedomOutbound.settings.domainStrategy;
  1080. },
  1081. set: function (newValue) {
  1082. newTemplateSettings = this.templateSettings;
  1083. freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && o.tag == "direct");
  1084. if (freedomOutboundIndex == -1) {
  1085. newTemplateSettings.outbounds.push({ protocol: "freedom", tag: "direct", settings: { "domainStrategy": newValue } });
  1086. } else if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) {
  1087. newTemplateSettings.outbounds[freedomOutboundIndex].settings = { "domainStrategy": newValue };
  1088. } else {
  1089. newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue;
  1090. }
  1091. this.templateSettings = newTemplateSettings;
  1092. }
  1093. },
  1094. routingStrategy: {
  1095. get: function () {
  1096. if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing.domainStrategy) return "AsIs";
  1097. return this.templateSettings.routing.domainStrategy;
  1098. },
  1099. set: function (newValue) {
  1100. newTemplateSettings = this.templateSettings;
  1101. newTemplateSettings.routing.domainStrategy = newValue;
  1102. this.templateSettings = newTemplateSettings;
  1103. }
  1104. },
  1105. logLevel: {
  1106. get: function () {
  1107. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
  1108. return this.templateSettings.log.loglevel;
  1109. },
  1110. set: function (newValue) {
  1111. newTemplateSettings = this.templateSettings;
  1112. newTemplateSettings.log.loglevel = newValue;
  1113. this.templateSettings = newTemplateSettings;
  1114. }
  1115. },
  1116. accessLog: {
  1117. get: function () {
  1118. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "";
  1119. return this.templateSettings.log.access;
  1120. },
  1121. set: function (newValue) {
  1122. newTemplateSettings = this.templateSettings;
  1123. newTemplateSettings.log.access = newValue;
  1124. this.templateSettings = newTemplateSettings;
  1125. }
  1126. },
  1127. errorLog: {
  1128. get: function () {
  1129. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
  1130. return this.templateSettings.log.error;
  1131. },
  1132. set: function (newValue) {
  1133. newTemplateSettings = this.templateSettings;
  1134. newTemplateSettings.log.error = newValue;
  1135. this.templateSettings = newTemplateSettings;
  1136. }
  1137. },
  1138. dnslog: {
  1139. get: function () {
  1140. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.dnsLog) return false;
  1141. return this.templateSettings.log.dnsLog;
  1142. },
  1143. set: function (newValue) {
  1144. newTemplateSettings = this.templateSettings;
  1145. newTemplateSettings.log.dnsLog = newValue;
  1146. this.templateSettings = newTemplateSettings;
  1147. }
  1148. },
  1149. statsInboundUplink: {
  1150. get: function () {
  1151. if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy.system.statsInboundUplink) return false;
  1152. return this.templateSettings.policy.system.statsInboundUplink;
  1153. },
  1154. set: function (newValue) {
  1155. newTemplateSettings = this.templateSettings;
  1156. newTemplateSettings.policy.system.statsInboundUplink = newValue;
  1157. this.templateSettings = newTemplateSettings;
  1158. }
  1159. },
  1160. statsInboundDownlink: {
  1161. get: function () {
  1162. if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy.system.statsInboundDownlink) return false;
  1163. return this.templateSettings.policy.system.statsInboundDownlink;
  1164. },
  1165. set: function (newValue) {
  1166. newTemplateSettings = this.templateSettings;
  1167. newTemplateSettings.policy.system.statsInboundDownlink = newValue;
  1168. this.templateSettings = newTemplateSettings;
  1169. }
  1170. },
  1171. statsOutboundUplink: {
  1172. get: function () {
  1173. if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy.system.statsOutboundUplink) return false;
  1174. return this.templateSettings.policy.system.statsOutboundUplink;
  1175. },
  1176. set: function (newValue) {
  1177. newTemplateSettings = this.templateSettings;
  1178. newTemplateSettings.policy.system.statsOutboundUplink = newValue;
  1179. this.templateSettings = newTemplateSettings;
  1180. }
  1181. },
  1182. statsOutboundDownlink: {
  1183. get: function () {
  1184. if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy.system.statsOutboundDownlink) return false;
  1185. return this.templateSettings.policy.system.statsOutboundDownlink;
  1186. },
  1187. set: function (newValue) {
  1188. newTemplateSettings = this.templateSettings;
  1189. newTemplateSettings.policy.system.statsOutboundDownlink = newValue;
  1190. this.templateSettings = newTemplateSettings;
  1191. }
  1192. },
  1193. maskAddressLog: {
  1194. get: function () {
  1195. if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.maskAddress) return "";
  1196. return this.templateSettings.log.maskAddress;
  1197. },
  1198. set: function (newValue) {
  1199. newTemplateSettings = this.templateSettings;
  1200. newTemplateSettings.log.maskAddress = newValue;
  1201. this.templateSettings = newTemplateSettings;
  1202. }
  1203. },
  1204. blockedIPs: {
  1205. get: function () {
  1206. return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
  1207. },
  1208. set: function (newValue) {
  1209. this.templateRuleSetter({ outboundTag: "blocked", property: "ip", data: newValue });
  1210. }
  1211. },
  1212. blockedDomains: {
  1213. get: function () {
  1214. return this.templateRuleGetter({ outboundTag: "blocked", property: "domain" });
  1215. },
  1216. set: function (newValue) {
  1217. this.templateRuleSetter({ outboundTag: "blocked", property: "domain", data: newValue });
  1218. }
  1219. },
  1220. blockedProtocols: {
  1221. get: function () {
  1222. return this.templateRuleGetter({ outboundTag: "blocked", property: "protocol" });
  1223. },
  1224. set: function (newValue) {
  1225. this.templateRuleSetter({ outboundTag: "blocked", property: "protocol", data: newValue });
  1226. }
  1227. },
  1228. directIPs: {
  1229. get: function () {
  1230. return this.templateRuleGetter({ outboundTag: "direct", property: "ip" });
  1231. },
  1232. set: function (newValue) {
  1233. this.templateRuleSetter({ outboundTag: "direct", property: "ip", data: newValue });
  1234. this.syncRulesWithOutbound("direct", this.directSettings);
  1235. }
  1236. },
  1237. directDomains: {
  1238. get: function () {
  1239. return this.templateRuleGetter({ outboundTag: "direct", property: "domain" });
  1240. },
  1241. set: function (newValue) {
  1242. this.templateRuleSetter({ outboundTag: "direct", property: "domain", data: newValue });
  1243. this.syncRulesWithOutbound("direct", this.directSettings);
  1244. }
  1245. },
  1246. ipv4Domains: {
  1247. get: function () {
  1248. return this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
  1249. },
  1250. set: function (newValue) {
  1251. this.templateRuleSetter({ outboundTag: "IPv4", property: "domain", data: newValue });
  1252. this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
  1253. }
  1254. },
  1255. warpDomains: {
  1256. get: function () {
  1257. return this.templateRuleGetter({ outboundTag: "warp", property: "domain" });
  1258. },
  1259. set: function (newValue) {
  1260. this.templateRuleSetter({ outboundTag: "warp", property: "domain", data: newValue });
  1261. }
  1262. },
  1263. torrentSettings: {
  1264. get: function () {
  1265. return ArrayUtils.doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
  1266. },
  1267. set: function (newValue) {
  1268. if (newValue) {
  1269. this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent];
  1270. } else {
  1271. this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent.includes(data));
  1272. }
  1273. },
  1274. },
  1275. WarpExist: {
  1276. get: function () {
  1277. return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp") >= 0 : false;
  1278. },
  1279. },
  1280. enableDNS: {
  1281. get: function () {
  1282. return this.templateSettings ? this.templateSettings.dns != null : false;
  1283. },
  1284. set: function (newValue) {
  1285. newTemplateSettings = this.templateSettings;
  1286. if (newValue) {
  1287. newTemplateSettings.dns = {
  1288. servers: [],
  1289. queryStrategy: "UseIP",
  1290. tag: "dns_inbound"
  1291. };
  1292. newTemplateSettings.fakedns = null;
  1293. } else {
  1294. delete newTemplateSettings.dns;
  1295. delete newTemplateSettings.fakedns;
  1296. }
  1297. this.templateSettings = newTemplateSettings;
  1298. }
  1299. },
  1300. dnsTag: {
  1301. get: function () {
  1302. return this.enableDNS ? this.templateSettings.dns.tag : "";
  1303. },
  1304. set: function (newValue) {
  1305. newTemplateSettings = this.templateSettings;
  1306. newTemplateSettings.dns.tag = newValue;
  1307. this.templateSettings = newTemplateSettings;
  1308. }
  1309. },
  1310. dnsClientIp: {
  1311. get: function () {
  1312. return this.enableDNS ? this.templateSettings.dns.clientIp : null;
  1313. },
  1314. set: function (newValue) {
  1315. newTemplateSettings = this.templateSettings;
  1316. if (newValue) {
  1317. newTemplateSettings.dns.clientIp = newValue;
  1318. } else {
  1319. delete newTemplateSettings.dns.clientIp;
  1320. }
  1321. this.templateSettings = newTemplateSettings;
  1322. }
  1323. },
  1324. dnsDisableCache: {
  1325. get: function () {
  1326. return this.enableDNS ? this.templateSettings.dns.disableCache : false;
  1327. },
  1328. set: function (newValue) {
  1329. newTemplateSettings = this.templateSettings;
  1330. if (newValue) {
  1331. newTemplateSettings.dns.disableCache = newValue;
  1332. } else {
  1333. delete newTemplateSettings.dns.disableCache
  1334. }
  1335. this.templateSettings = newTemplateSettings;
  1336. }
  1337. },
  1338. dnsDisableFallback: {
  1339. get: function () {
  1340. return this.enableDNS ? this.templateSettings.dns.disableFallback : false;
  1341. },
  1342. set: function (newValue) {
  1343. newTemplateSettings = this.templateSettings;
  1344. if (newValue) {
  1345. newTemplateSettings.dns.disableFallback = newValue;
  1346. } else {
  1347. delete newTemplateSettings.dns.disableFallback
  1348. }
  1349. this.templateSettings = newTemplateSettings;
  1350. }
  1351. },
  1352. dnsDisableFallbackIfMatch: {
  1353. get: function () {
  1354. return this.enableDNS ? this.templateSettings.dns.disableFallbackIfMatch : false;
  1355. },
  1356. set: function (newValue) {
  1357. newTemplateSettings = this.templateSettings;
  1358. if (newValue) {
  1359. newTemplateSettings.dns.disableFallbackIfMatch = newValue;
  1360. } else {
  1361. delete newTemplateSettings.dns.disableFallbackIfMatch
  1362. }
  1363. this.templateSettings = newTemplateSettings;
  1364. }
  1365. },
  1366. dnsUseSystemHosts: {
  1367. get: function () {
  1368. return this.enableDNS ? this.templateSettings.dns.useSystemHosts : false;
  1369. },
  1370. set: function (newValue) {
  1371. newTemplateSettings = this.templateSettings;
  1372. if (newValue) {
  1373. newTemplateSettings.dns.useSystemHosts = newValue;
  1374. } else {
  1375. delete newTemplateSettings.dns.useSystemHosts
  1376. }
  1377. this.templateSettings = newTemplateSettings;
  1378. }
  1379. },
  1380. dnsStrategy: {
  1381. get: function () {
  1382. return this.enableDNS ? this.templateSettings.dns.queryStrategy : null;
  1383. },
  1384. set: function (newValue) {
  1385. newTemplateSettings = this.templateSettings;
  1386. newTemplateSettings.dns.queryStrategy = newValue;
  1387. this.templateSettings = newTemplateSettings;
  1388. }
  1389. },
  1390. dnsServers: {
  1391. get: function () { return this.enableDNS ? this.templateSettings.dns.servers : []; },
  1392. set: function (newValue) {
  1393. newTemplateSettings = this.templateSettings;
  1394. newTemplateSettings.dns.servers = newValue;
  1395. this.templateSettings = newTemplateSettings;
  1396. }
  1397. },
  1398. fakeDns: {
  1399. get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; },
  1400. set: function (newValue) {
  1401. newTemplateSettings = this.templateSettings;
  1402. if (this.enableDNS) {
  1403. newTemplateSettings.fakedns = newValue.length > 0 ? newValue : null;
  1404. } else {
  1405. delete newTemplateSettings.fakedns;
  1406. }
  1407. this.templateSettings = newTemplateSettings;
  1408. }
  1409. }
  1410. },
  1411. });
  1412. </script>
  1413. {{ template "page/body_end" .}}