xray.html 59 KB

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