inbound_modal.html 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. {{define "modals/inboundModal"}}
  2. <a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" :dialog-style="{ top: '20px' }"
  3. @ok="inModal.ok" :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
  4. :class="themeSwitcher.currentTheme" :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
  5. {{template "form/inbound"}}
  6. </a-modal>
  7. <script>
  8. // Make inModal globally available to ensure it works with any base path
  9. const inModal = (window.inModal = {
  10. title: "",
  11. visible: false,
  12. confirmLoading: false,
  13. okText: '{{ i18n "sure" }}',
  14. isEdit: false,
  15. confirm: null,
  16. inbound: new Inbound(),
  17. dbInbound: new DBInbound(),
  18. ok() {
  19. ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
  20. },
  21. show({
  22. title = "",
  23. okText = '{{ i18n "sure" }}',
  24. inbound = null,
  25. dbInbound = null,
  26. confirm = (inbound, dbInbound) => { },
  27. isEdit = false,
  28. }) {
  29. this.title = title;
  30. this.okText = okText;
  31. if (inbound) {
  32. this.inbound = Inbound.fromJson(inbound.toJson());
  33. } else {
  34. this.inbound = new Inbound();
  35. }
  36. // Always ensure testseed is initialized for VLESS protocol (even if vision flow is not set yet)
  37. // This ensures Vue reactivity works properly
  38. if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) {
  39. if (
  40. !this.inbound.settings.testseed ||
  41. !Array.isArray(this.inbound.settings.testseed) ||
  42. this.inbound.settings.testseed.length < 4
  43. ) {
  44. // Create a new array to ensure Vue reactivity
  45. this.inbound.settings.testseed = [900, 500, 900, 256].slice();
  46. }
  47. }
  48. if (dbInbound) {
  49. this.dbInbound = new DBInbound(dbInbound);
  50. } else {
  51. this.dbInbound = new DBInbound();
  52. }
  53. this.confirm = confirm;
  54. this.visible = true;
  55. this.isEdit = isEdit;
  56. },
  57. close() {
  58. inModal.visible = false;
  59. inModal.loading(false);
  60. },
  61. loading(loading = true) {
  62. inModal.confirmLoading = loading;
  63. },
  64. // Vision Seed methods - always available regardless of Vue context
  65. updateTestseed(index, value) {
  66. // Use inModal.inbound explicitly to ensure correct context
  67. if (!inModal.inbound || !inModal.inbound.settings) return;
  68. // Ensure testseed is initialized
  69. if (
  70. !inModal.inbound.settings.testseed ||
  71. !Array.isArray(inModal.inbound.settings.testseed)
  72. ) {
  73. inModal.inbound.settings.testseed = [900, 500, 900, 256];
  74. }
  75. // Ensure array has enough elements
  76. while (inModal.inbound.settings.testseed.length <= index) {
  77. inModal.inbound.settings.testseed.push(0);
  78. }
  79. // Update value
  80. inModal.inbound.settings.testseed[index] = value;
  81. },
  82. setRandomTestseed() {
  83. // Use inModal.inbound explicitly to ensure correct context
  84. if (!inModal.inbound || !inModal.inbound.settings) return;
  85. // Ensure testseed is initialized
  86. if (
  87. !inModal.inbound.settings.testseed ||
  88. !Array.isArray(inModal.inbound.settings.testseed) ||
  89. inModal.inbound.settings.testseed.length < 4
  90. ) {
  91. inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
  92. }
  93. // Create new array with random values
  94. inModal.inbound.settings.testseed = [
  95. Math.floor(Math.random() * 1000),
  96. Math.floor(Math.random() * 1000),
  97. Math.floor(Math.random() * 1000),
  98. Math.floor(Math.random() * 1000),
  99. ];
  100. },
  101. resetTestseed() {
  102. // Use inModal.inbound explicitly to ensure correct context
  103. if (!inModal.inbound || !inModal.inbound.settings) return;
  104. // Reset testseed to default values
  105. inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
  106. },
  107. });
  108. // Store Vue instance globally to ensure methods are always accessible
  109. let inboundModalVueInstance = null;
  110. inboundModalVueInstance = new Vue({
  111. delimiters: ["[[", "]]"],
  112. el: "#inbound-modal",
  113. data: {
  114. inModal: inModal,
  115. delayedStart: false,
  116. get inbound() {
  117. return inModal.inbound;
  118. },
  119. get dbInbound() {
  120. return inModal.dbInbound;
  121. },
  122. get isEdit() {
  123. return inModal.isEdit;
  124. },
  125. get client() {
  126. return inModal.inbound &&
  127. inModal.inbound.clients &&
  128. inModal.inbound.clients.length > 0
  129. ? inModal.inbound.clients[0]
  130. : null;
  131. },
  132. get datepicker() {
  133. return app.datepicker;
  134. },
  135. get delayedExpireDays() {
  136. return this.client && this.client.expiryTime < 0
  137. ? this.client.expiryTime / -86400000
  138. : 0;
  139. },
  140. set delayedExpireDays(days) {
  141. this.client.expiryTime = -86400000 * days;
  142. },
  143. get externalProxy() {
  144. return this.inbound.stream.externalProxy.length > 0;
  145. },
  146. set externalProxy(value) {
  147. if (value) {
  148. inModal.inbound.stream.externalProxy = [
  149. {
  150. forceTls: "same",
  151. dest: window.location.hostname,
  152. port: inModal.inbound.port,
  153. remark: "",
  154. },
  155. ];
  156. } else {
  157. inModal.inbound.stream.externalProxy = [];
  158. }
  159. },
  160. },
  161. watch: {
  162. "inModal.inbound.stream.security"(newVal, oldVal) {
  163. // Clear flow when security changes from reality/tls to none
  164. if (
  165. inModal.inbound.protocol == Protocols.VLESS &&
  166. !inModal.inbound.canEnableTlsFlow()
  167. ) {
  168. inModal.inbound.settings.vlesses.forEach((client) => {
  169. client.flow = "";
  170. });
  171. }
  172. },
  173. // Ensure testseed is always initialized when vision flow is enabled
  174. "inModal.inbound.settings.vlesses": {
  175. handler() {
  176. if (
  177. inModal.inbound.protocol === Protocols.VLESS &&
  178. inModal.inbound.settings &&
  179. inModal.inbound.settings.vlesses
  180. ) {
  181. const hasVisionFlow = inModal.inbound.settings.vlesses.some(
  182. (c) =>
  183. c.flow === "xtls-rprx-vision" ||
  184. c.flow === "xtls-rprx-vision-udp443",
  185. );
  186. if (
  187. hasVisionFlow &&
  188. (!inModal.inbound.settings.testseed ||
  189. !Array.isArray(inModal.inbound.settings.testseed) ||
  190. inModal.inbound.settings.testseed.length < 4)
  191. ) {
  192. inModal.inbound.settings.testseed = [900, 500, 900, 256];
  193. }
  194. }
  195. },
  196. deep: true,
  197. },
  198. },
  199. methods: {
  200. streamNetworkChange() {
  201. if (!inModal.inbound.canEnableTls()) {
  202. this.inModal.inbound.stream.security = "none";
  203. }
  204. if (!inModal.inbound.canEnableReality()) {
  205. this.inModal.inbound.reality = false;
  206. }
  207. if (
  208. this.inModal.inbound.protocol == Protocols.VLESS &&
  209. !inModal.inbound.canEnableTlsFlow()
  210. ) {
  211. this.inModal.inbound.settings.vlesses.forEach((client) => {
  212. client.flow = "";
  213. });
  214. }
  215. if (inModal.inbound.stream.network != "kcp") {
  216. inModal.inbound.stream.finalmask.udp = [];
  217. }
  218. },
  219. SSMethodChange() {
  220. this.inModal.inbound.settings.password =
  221. RandomUtil.randomShadowsocksPassword(
  222. this.inModal.inbound.settings.method,
  223. );
  224. if (this.inModal.inbound.isSSMultiUser) {
  225. if (this.inModal.inbound.settings.shadowsockses.length == 0) {
  226. this.inModal.inbound.settings.shadowsockses = [
  227. new Inbound.ShadowsocksSettings.Shadowsocks(),
  228. ];
  229. }
  230. if (!this.inModal.inbound.isSS2022) {
  231. this.inModal.inbound.settings.shadowsockses.forEach((client) => {
  232. client.method = this.inModal.inbound.settings.method;
  233. });
  234. } else {
  235. this.inModal.inbound.settings.shadowsockses.forEach((client) => {
  236. client.method = "";
  237. });
  238. }
  239. this.inModal.inbound.settings.shadowsockses.forEach((client) => {
  240. client.password = RandomUtil.randomShadowsocksPassword(
  241. this.inModal.inbound.settings.method,
  242. );
  243. });
  244. } else {
  245. if (this.inModal.inbound.settings.shadowsockses.length > 0) {
  246. this.inModal.inbound.settings.shadowsockses = [];
  247. }
  248. }
  249. },
  250. setDefaultCertData(index) {
  251. inModal.inbound.stream.tls.certs[index].certFile = app.defaultCert;
  252. inModal.inbound.stream.tls.certs[index].keyFile = app.defaultKey;
  253. },
  254. async getNewX25519Cert() {
  255. inModal.loading(true);
  256. const msg = await HttpUtil.get("/panel/api/server/getNewX25519Cert");
  257. inModal.loading(false);
  258. if (!msg.success) {
  259. return;
  260. }
  261. inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
  262. inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
  263. },
  264. clearX25519Cert() {
  265. this.inbound.stream.reality.privateKey = "";
  266. this.inbound.stream.reality.settings.publicKey = "";
  267. },
  268. async getNewmldsa65() {
  269. inModal.loading(true);
  270. const msg = await HttpUtil.get("/panel/api/server/getNewmldsa65");
  271. inModal.loading(false);
  272. if (!msg.success) {
  273. return;
  274. }
  275. inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
  276. inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
  277. },
  278. clearMldsa65() {
  279. this.inbound.stream.reality.mldsa65Seed = "";
  280. this.inbound.stream.reality.settings.mldsa65Verify = "";
  281. },
  282. randomizeRealityTarget() {
  283. if (typeof getRandomRealityTarget !== "undefined") {
  284. const randomTarget = getRandomRealityTarget();
  285. this.inbound.stream.reality.target = randomTarget.target;
  286. this.inbound.stream.reality.serverNames = randomTarget.sni;
  287. }
  288. },
  289. async getNewEchCert() {
  290. inModal.loading(true);
  291. const msg = await HttpUtil.post("/panel/api/server/getNewEchCert", {
  292. sni: inModal.inbound.stream.tls.sni,
  293. });
  294. inModal.loading(false);
  295. if (!msg.success) {
  296. return;
  297. }
  298. inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
  299. inModal.inbound.stream.tls.settings.echConfigList =
  300. msg.obj.echConfigList;
  301. },
  302. clearEchCert() {
  303. this.inbound.stream.tls.echServerKeys = "";
  304. this.inbound.stream.tls.settings.echConfigList = "";
  305. },
  306. async getNewVlessEnc() {
  307. inModal.loading(true);
  308. const msg = await HttpUtil.get("/panel/api/server/getNewVlessEnc");
  309. inModal.loading(false);
  310. if (!msg.success) {
  311. return;
  312. }
  313. const auths = msg.obj.auths || [];
  314. const selected = inModal.inbound.settings.selectedAuth;
  315. const block = auths.find((a) => a.label === selected);
  316. if (!block) {
  317. console.error("No auth block for", selected);
  318. return;
  319. }
  320. inModal.inbound.settings.decryption = block.decryption;
  321. inModal.inbound.settings.encryption = block.encryption;
  322. },
  323. clearVlessEnc() {
  324. this.inbound.settings.decryption = "none";
  325. this.inbound.settings.encryption = "none";
  326. this.inbound.settings.selectedAuth = undefined;
  327. },
  328. // Vision Seed methods - must be in Vue methods for proper binding
  329. updateTestseed(index, value) {
  330. // Ensure testseed is initialized
  331. if (
  332. !this.inbound.settings.testseed ||
  333. !Array.isArray(this.inbound.settings.testseed)
  334. ) {
  335. this.$set(this.inbound.settings, "testseed", [900, 500, 900, 256]);
  336. }
  337. // Ensure array has enough elements
  338. while (this.inbound.settings.testseed.length <= index) {
  339. this.inbound.settings.testseed.push(0);
  340. }
  341. // Update value using Vue.set for reactivity
  342. this.$set(this.inbound.settings.testseed, index, value);
  343. },
  344. setRandomTestseed() {
  345. // Create new array with random values and use Vue.set for reactivity
  346. const newSeed = [
  347. Math.floor(Math.random() * 1000),
  348. Math.floor(Math.random() * 1000),
  349. Math.floor(Math.random() * 1000),
  350. Math.floor(Math.random() * 1000),
  351. ];
  352. this.$set(this.inbound.settings, "testseed", newSeed);
  353. },
  354. resetTestseed() {
  355. // Reset testseed to default values using Vue.set for reactivity
  356. this.$set(this.inbound.settings, "testseed", [900, 500, 900, 256]);
  357. },
  358. },
  359. });
  360. </script>
  361. {{end}}