GeneralTab.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. <script setup>
  2. import { computed, onMounted, ref } from 'vue';
  3. import { useI18n } from 'vue-i18n';
  4. import { HttpUtil, LanguageManager } from '@/utils';
  5. import SettingListItem from '@/components/SettingListItem.vue';
  6. const { t } = useI18n();
  7. const props = defineProps({
  8. // Reactive AllSetting instance shared with the parent page.
  9. allSetting: { type: Object, required: true },
  10. });
  11. // Remark model — legacy stores it as a single string where index 0 is
  12. // the separator char and the rest is the order of model keys
  13. // (i=Inbound, e=Email, o=Other). Surface it as two v-models that read
  14. // and write the underlying string.
  15. const remarkModels = { i: 'Inbound', e: 'Email', o: 'Other' };
  16. const remarkSeparators = [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'];
  17. const remarkModel = computed({
  18. get: () => {
  19. const rm = props.allSetting.remarkModel || '';
  20. return rm.length > 1 ? rm.substring(1).split('') : [];
  21. },
  22. set: (value) => {
  23. const sep = (props.allSetting.remarkModel || '-').charAt(0);
  24. props.allSetting.remarkModel = sep + value.join('');
  25. },
  26. });
  27. const remarkSeparator = computed({
  28. get: () => {
  29. const rm = props.allSetting.remarkModel || '-';
  30. return rm.length > 1 ? rm.charAt(0) : '-';
  31. },
  32. set: (value) => {
  33. const tail = (props.allSetting.remarkModel || '-').substring(1);
  34. props.allSetting.remarkModel = value + tail;
  35. },
  36. });
  37. const remarkSample = computed(() => {
  38. const parts = remarkModel.value.map((k) => remarkModels[k]);
  39. return parts.length === 0 ? '' : parts.join(remarkSeparator.value);
  40. });
  41. const datepicker = computed({
  42. get: () => props.allSetting.datepicker || 'gregorian',
  43. set: (value) => { props.allSetting.datepicker = value; },
  44. });
  45. const datepickerList = [
  46. { name: 'Gregorian (Standard)', value: 'gregorian' },
  47. { name: 'Jalalian (شمسی)', value: 'jalalian' },
  48. ];
  49. // Language is stored client-side in a cookie, NOT in AllSetting. The
  50. // legacy panel reloads on change so the Go side renders templates in
  51. // the new language.
  52. const lang = ref(LanguageManager.getLanguage());
  53. function onLangChange() {
  54. LanguageManager.setLanguage(lang.value);
  55. }
  56. // LDAP inbound tags are CSV on the wire; expose as an array so the
  57. // multi-select v-model works directly.
  58. const ldapInboundTagList = computed({
  59. get: () => {
  60. const csv = props.allSetting.ldapInboundTags || '';
  61. return csv.length ? csv.split(',').map((s) => s.trim()).filter(Boolean) : [];
  62. },
  63. set: (list) => {
  64. props.allSetting.ldapInboundTags = Array.isArray(list) ? list.join(',') : '';
  65. },
  66. });
  67. const inboundOptions = ref([]);
  68. async function loadInboundTags() {
  69. const msg = await HttpUtil.get('/panel/api/inbounds/list');
  70. if (msg?.success && Array.isArray(msg.obj)) {
  71. inboundOptions.value = msg.obj.map((ib) => ({
  72. label: `${ib.tag} (${ib.protocol}@${ib.port})`,
  73. value: ib.tag,
  74. }));
  75. } else {
  76. inboundOptions.value = [];
  77. }
  78. }
  79. onMounted(loadInboundTags);
  80. </script>
  81. <template>
  82. <a-collapse default-active-key="1">
  83. <a-collapse-panel key="1" :header="t('pages.settings.panelSettings')">
  84. <SettingListItem paddings="small">
  85. <template #title>{{ t('pages.settings.remarkModel') }}</template>
  86. <template #description>{{ t('pages.settings.sampleRemark') }}: <i>#{{ remarkSample }}</i></template>
  87. <template #control>
  88. <a-input-group :style="{ width: '100%' }">
  89. <a-select v-model:value="remarkModel" mode="multiple"
  90. :style="{ paddingRight: '.5rem', minWidth: '80%', width: 'auto' }">
  91. <a-select-option v-for="(label, key) in remarkModels" :key="key" :value="key">
  92. {{ label }}
  93. </a-select-option>
  94. </a-select>
  95. <a-select v-model:value="remarkSeparator" :style="{ width: '20%' }">
  96. <a-select-option v-for="sep in remarkSeparators" :key="sep" :value="sep">{{ sep }}</a-select-option>
  97. </a-select>
  98. </a-input-group>
  99. </template>
  100. </SettingListItem>
  101. <SettingListItem paddings="small">
  102. <template #title>{{ t('pages.settings.panelListeningIP') }}</template>
  103. <template #description>{{ t('pages.settings.panelListeningIPDesc') }}</template>
  104. <template #control>
  105. <a-input v-model:value="allSetting.webListen" type="text" />
  106. </template>
  107. </SettingListItem>
  108. <SettingListItem paddings="small">
  109. <template #title>{{ t('pages.settings.panelListeningDomain') }}</template>
  110. <template #description>{{ t('pages.settings.panelListeningDomainDesc') }}</template>
  111. <template #control>
  112. <a-input v-model:value="allSetting.webDomain" type="text" />
  113. </template>
  114. </SettingListItem>
  115. <SettingListItem paddings="small">
  116. <template #title>{{ t('pages.settings.panelPort') }}</template>
  117. <template #description>{{ t('pages.settings.panelPortDesc') }}</template>
  118. <template #control>
  119. <a-input-number v-model:value="allSetting.webPort" :min="1" :max="65535" :style="{ width: '100%' }" />
  120. </template>
  121. </SettingListItem>
  122. <SettingListItem paddings="small">
  123. <template #title>{{ t('pages.settings.panelUrlPath') }}</template>
  124. <template #description>{{ t('pages.settings.panelUrlPathDesc') }}</template>
  125. <template #control>
  126. <a-input v-model:value="allSetting.webBasePath" type="text" />
  127. </template>
  128. </SettingListItem>
  129. <SettingListItem paddings="small">
  130. <template #title>{{ t('pages.settings.sessionMaxAge') }}</template>
  131. <template #description>{{ t('pages.settings.sessionMaxAgeDesc') }}</template>
  132. <template #control>
  133. <a-input-number v-model:value="allSetting.sessionMaxAge" :min="60" :style="{ width: '100%' }" />
  134. </template>
  135. </SettingListItem>
  136. <SettingListItem paddings="small">
  137. <template #title>Trusted proxy CIDRs</template>
  138. <template #description>Comma-separated IPs/CIDRs allowed to set forwarded host, proto, and client IP headers.</template>
  139. <template #control>
  140. <a-input v-model:value="allSetting.trustedProxyCIDRs" placeholder="127.0.0.1/32,::1/128" />
  141. </template>
  142. </SettingListItem>
  143. <SettingListItem paddings="small">
  144. <template #title>{{ t('pages.settings.pageSize') }}</template>
  145. <template #description>{{ t('pages.settings.pageSizeDesc') }}</template>
  146. <template #control>
  147. <a-input-number v-model:value="allSetting.pageSize" :min="0" :step="5" :style="{ width: '100%' }" />
  148. </template>
  149. </SettingListItem>
  150. <SettingListItem paddings="small">
  151. <template #title>{{ t('pages.settings.language') }}</template>
  152. <template #control>
  153. <a-select v-model:value="lang" :style="{ width: '100%' }" @change="onLangChange">
  154. <a-select-option v-for="l in LanguageManager.supportedLanguages" :key="l.value" :value="l.value"
  155. :label="l.value">
  156. <span role="img" :aria-label="l.name">{{ l.icon }}</span>
  157. &nbsp;&nbsp;<span>{{ l.name }}</span>
  158. </a-select-option>
  159. </a-select>
  160. </template>
  161. </SettingListItem>
  162. </a-collapse-panel>
  163. <a-collapse-panel key="2" :header="t('pages.settings.notifications')">
  164. <SettingListItem paddings="small">
  165. <template #title>{{ t('pages.settings.expireTimeDiff') }}</template>
  166. <template #description>{{ t('pages.settings.expireTimeDiffDesc') }}</template>
  167. <template #control>
  168. <a-input-number v-model:value="allSetting.expireDiff" :min="0" :style="{ width: '100%' }" />
  169. </template>
  170. </SettingListItem>
  171. <SettingListItem paddings="small">
  172. <template #title>{{ t('pages.settings.trafficDiff') }}</template>
  173. <template #description>{{ t('pages.settings.trafficDiffDesc') }}</template>
  174. <template #control>
  175. <a-input-number v-model:value="allSetting.trafficDiff" :min="0" :style="{ width: '100%' }" />
  176. </template>
  177. </SettingListItem>
  178. </a-collapse-panel>
  179. <a-collapse-panel key="3" :header="t('pages.settings.certs')">
  180. <SettingListItem paddings="small">
  181. <template #title>{{ t('pages.settings.publicKeyPath') }}</template>
  182. <template #description>{{ t('pages.settings.publicKeyPathDesc') }}</template>
  183. <template #control>
  184. <a-input v-model:value="allSetting.webCertFile" type="text" />
  185. </template>
  186. </SettingListItem>
  187. <SettingListItem paddings="small">
  188. <template #title>{{ t('pages.settings.privateKeyPath') }}</template>
  189. <template #description>{{ t('pages.settings.privateKeyPathDesc') }}</template>
  190. <template #control>
  191. <a-input v-model:value="allSetting.webKeyFile" type="text" />
  192. </template>
  193. </SettingListItem>
  194. </a-collapse-panel>
  195. <a-collapse-panel key="4" :header="t('pages.settings.externalTraffic')">
  196. <SettingListItem paddings="small">
  197. <template #title>{{ t('pages.settings.externalTrafficInformEnable') }}</template>
  198. <template #description>{{ t('pages.settings.externalTrafficInformEnableDesc') }}</template>
  199. <template #control>
  200. <a-switch v-model:checked="allSetting.externalTrafficInformEnable" />
  201. </template>
  202. </SettingListItem>
  203. <SettingListItem paddings="small">
  204. <template #title>{{ t('pages.settings.externalTrafficInformURI') }}</template>
  205. <template #description>{{ t('pages.settings.externalTrafficInformURIDesc') }}</template>
  206. <template #control>
  207. <a-input v-model:value="allSetting.externalTrafficInformURI" placeholder="(http|https)://domain[:port]/path/"
  208. type="text" />
  209. </template>
  210. </SettingListItem>
  211. <SettingListItem paddings="small">
  212. <template #title>{{ t('pages.settings.restartXrayOnClientDisable') }}</template>
  213. <template #description>{{ t('pages.settings.restartXrayOnClientDisableDesc') }}</template>
  214. <template #control>
  215. <a-switch v-model:checked="allSetting.restartXrayOnClientDisable" />
  216. </template>
  217. </SettingListItem>
  218. </a-collapse-panel>
  219. <a-collapse-panel key="5" :header="t('pages.settings.dateAndTime')">
  220. <SettingListItem paddings="small">
  221. <template #title>{{ t('pages.settings.timeZone') }}</template>
  222. <template #description>{{ t('pages.settings.timeZoneDesc') }}</template>
  223. <template #control>
  224. <a-input v-model:value="allSetting.timeLocation" type="text" />
  225. </template>
  226. </SettingListItem>
  227. <SettingListItem paddings="small">
  228. <template #title>{{ t('pages.settings.datepicker') }}</template>
  229. <template #description>{{ t('pages.settings.datepickerDescription') }}</template>
  230. <template #control>
  231. <a-select v-model:value="datepicker" :style="{ width: '100%' }">
  232. <a-select-option v-for="item in datepickerList" :key="item.value" :value="item.value">
  233. {{ item.name }}
  234. </a-select-option>
  235. </a-select>
  236. </template>
  237. </SettingListItem>
  238. </a-collapse-panel>
  239. <a-collapse-panel key="6" header="LDAP">
  240. <SettingListItem paddings="small">
  241. <template #title>Enable LDAP sync</template>
  242. <template #control>
  243. <a-switch v-model:checked="allSetting.ldapEnable" />
  244. </template>
  245. </SettingListItem>
  246. <SettingListItem paddings="small">
  247. <template #title>LDAP host</template>
  248. <template #control>
  249. <a-input v-model:value="allSetting.ldapHost" type="text" />
  250. </template>
  251. </SettingListItem>
  252. <SettingListItem paddings="small">
  253. <template #title>LDAP port</template>
  254. <template #control>
  255. <a-input-number v-model:value="allSetting.ldapPort" :min="1" :max="65535" :style="{ width: '100%' }" />
  256. </template>
  257. </SettingListItem>
  258. <SettingListItem paddings="small">
  259. <template #title>Use TLS (LDAPS)</template>
  260. <template #control>
  261. <a-switch v-model:checked="allSetting.ldapUseTLS" />
  262. </template>
  263. </SettingListItem>
  264. <SettingListItem paddings="small">
  265. <template #title>Bind DN</template>
  266. <template #control>
  267. <a-input v-model:value="allSetting.ldapBindDN" type="text" />
  268. </template>
  269. </SettingListItem>
  270. <SettingListItem paddings="small">
  271. <template #title>{{ t('password') }}</template>
  272. <template #description>
  273. {{ allSetting.hasLdapPassword ? 'Configured; leave blank to keep current password.' : 'Not configured.' }}
  274. </template>
  275. <template #control>
  276. <a-input-password v-model:value="allSetting.ldapPassword"
  277. :placeholder="allSetting.hasLdapPassword ? 'Configured - enter a new value to replace' : ''" />
  278. </template>
  279. </SettingListItem>
  280. <SettingListItem paddings="small">
  281. <template #title>Base DN</template>
  282. <template #control>
  283. <a-input v-model:value="allSetting.ldapBaseDN" type="text" />
  284. </template>
  285. </SettingListItem>
  286. <SettingListItem paddings="small">
  287. <template #title>User filter</template>
  288. <template #control>
  289. <a-input v-model:value="allSetting.ldapUserFilter" type="text" />
  290. </template>
  291. </SettingListItem>
  292. <SettingListItem paddings="small">
  293. <template #title>User attribute (username/email)</template>
  294. <template #control>
  295. <a-input v-model:value="allSetting.ldapUserAttr" type="text" />
  296. </template>
  297. </SettingListItem>
  298. <SettingListItem paddings="small">
  299. <template #title>VLESS flag attribute</template>
  300. <template #control>
  301. <a-input v-model:value="allSetting.ldapVlessField" type="text" />
  302. </template>
  303. </SettingListItem>
  304. <SettingListItem paddings="small">
  305. <template #title>Generic flag attribute (optional)</template>
  306. <template #description>If set, overrides VLESS flag — e.g. shadowInactive.</template>
  307. <template #control>
  308. <a-input v-model:value="allSetting.ldapFlagField" type="text" />
  309. </template>
  310. </SettingListItem>
  311. <SettingListItem paddings="small">
  312. <template #title>Truthy values</template>
  313. <template #description>Comma-separated; default: true,1,yes,on</template>
  314. <template #control>
  315. <a-input v-model:value="allSetting.ldapTruthyValues" type="text" />
  316. </template>
  317. </SettingListItem>
  318. <SettingListItem paddings="small">
  319. <template #title>Invert flag</template>
  320. <template #description>Enable when the attribute means disabled (e.g. shadowInactive).</template>
  321. <template #control>
  322. <a-switch v-model:checked="allSetting.ldapInvertFlag" />
  323. </template>
  324. </SettingListItem>
  325. <SettingListItem paddings="small">
  326. <template #title>Sync schedule</template>
  327. <template #description>Cron-like string, e.g. @every 1m</template>
  328. <template #control>
  329. <a-input v-model:value="allSetting.ldapSyncCron" type="text" />
  330. </template>
  331. </SettingListItem>
  332. <SettingListItem paddings="small">
  333. <template #title>Inbound tags</template>
  334. <template #description>Inbounds that LDAP sync may auto-create or auto-delete clients on.</template>
  335. <template #control>
  336. <a-select v-model:value="ldapInboundTagList" mode="multiple" :style="{ width: '100%' }">
  337. <a-select-option v-for="opt in inboundOptions" :key="opt.value" :value="opt.value">
  338. {{ opt.label }}
  339. </a-select-option>
  340. </a-select>
  341. <div v-if="inboundOptions.length === 0" class="ldap-no-inbounds">
  342. No inbounds found. Create one in Inbounds first.
  343. </div>
  344. </template>
  345. </SettingListItem>
  346. <SettingListItem paddings="small">
  347. <template #title>Auto create clients</template>
  348. <template #control>
  349. <a-switch v-model:checked="allSetting.ldapAutoCreate" />
  350. </template>
  351. </SettingListItem>
  352. <SettingListItem paddings="small">
  353. <template #title>Auto delete clients</template>
  354. <template #control>
  355. <a-switch v-model:checked="allSetting.ldapAutoDelete" />
  356. </template>
  357. </SettingListItem>
  358. <SettingListItem paddings="small">
  359. <template #title>Default total (GB)</template>
  360. <template #control>
  361. <a-input-number v-model:value="allSetting.ldapDefaultTotalGB" :min="0" :style="{ width: '100%' }" />
  362. </template>
  363. </SettingListItem>
  364. <SettingListItem paddings="small">
  365. <template #title>Default expiry (days)</template>
  366. <template #control>
  367. <a-input-number v-model:value="allSetting.ldapDefaultExpiryDays" :min="0" :style="{ width: '100%' }" />
  368. </template>
  369. </SettingListItem>
  370. <SettingListItem paddings="small">
  371. <template #title>Default IP limit</template>
  372. <template #control>
  373. <a-input-number v-model:value="allSetting.ldapDefaultLimitIP" :min="0" :style="{ width: '100%' }" />
  374. </template>
  375. </SettingListItem>
  376. </a-collapse-panel>
  377. </a-collapse>
  378. </template>
  379. <style scoped>
  380. .ldap-no-inbounds {
  381. margin-top: 6px;
  382. color: #999;
  383. font-size: 12px;
  384. }
  385. </style>