General.svelte 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. <script lang="ts">
  2. import DOMPurify from 'dompurify';
  3. import { getVersionUpdates, getWebhookUrl, updateWebhookUrl } from '$lib/apis';
  4. import {
  5. getAdminConfig,
  6. getLdapConfig,
  7. getLdapServer,
  8. updateAdminConfig,
  9. updateLdapConfig,
  10. updateLdapServer
  11. } from '$lib/apis/auths';
  12. import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
  13. import Switch from '$lib/components/common/Switch.svelte';
  14. import Tooltip from '$lib/components/common/Tooltip.svelte';
  15. import { WEBUI_BUILD_HASH, WEBUI_VERSION } from '$lib/constants';
  16. import { config, showChangelog } from '$lib/stores';
  17. import { compareVersion } from '$lib/utils';
  18. import { onMount, getContext } from 'svelte';
  19. import { toast } from 'svelte-sonner';
  20. import Textarea from '$lib/components/common/Textarea.svelte';
  21. const i18n = getContext('i18n');
  22. export let saveHandler: Function;
  23. let updateAvailable = null;
  24. let version = {
  25. current: '',
  26. latest: ''
  27. };
  28. let adminConfig = null;
  29. let webhookUrl = '';
  30. // LDAP
  31. let ENABLE_LDAP = false;
  32. let LDAP_SERVER = {
  33. label: '',
  34. host: '',
  35. port: '',
  36. attribute_for_mail: 'mail',
  37. attribute_for_username: 'uid',
  38. app_dn: '',
  39. app_dn_password: '',
  40. search_base: '',
  41. search_filters: '',
  42. use_tls: false,
  43. certificate_path: '',
  44. ciphers: ''
  45. };
  46. const checkForVersionUpdates = async () => {
  47. updateAvailable = null;
  48. version = await getVersionUpdates(localStorage.token).catch((error) => {
  49. return {
  50. current: WEBUI_VERSION,
  51. latest: WEBUI_VERSION
  52. };
  53. });
  54. console.info(version);
  55. updateAvailable = compareVersion(version.latest, version.current);
  56. console.info(updateAvailable);
  57. };
  58. const updateLdapServerHandler = async () => {
  59. if (!ENABLE_LDAP) return;
  60. const res = await updateLdapServer(localStorage.token, LDAP_SERVER).catch((error) => {
  61. toast.error(`${error}`);
  62. return null;
  63. });
  64. if (res) {
  65. toast.success($i18n.t('LDAP server updated'));
  66. }
  67. };
  68. const updateHandler = async () => {
  69. webhookUrl = await updateWebhookUrl(localStorage.token, webhookUrl);
  70. const res = await updateAdminConfig(localStorage.token, adminConfig);
  71. await updateLdapConfig(localStorage.token, ENABLE_LDAP);
  72. await updateLdapServerHandler();
  73. if (res) {
  74. saveHandler();
  75. } else {
  76. toast.error($i18n.t('Failed to update settings'));
  77. }
  78. };
  79. onMount(async () => {
  80. if ($config?.features?.enable_version_update_check) {
  81. checkForVersionUpdates();
  82. }
  83. await Promise.all([
  84. (async () => {
  85. adminConfig = await getAdminConfig(localStorage.token);
  86. })(),
  87. (async () => {
  88. webhookUrl = await getWebhookUrl(localStorage.token);
  89. })(),
  90. (async () => {
  91. LDAP_SERVER = await getLdapServer(localStorage.token);
  92. })()
  93. ]);
  94. const ldapConfig = await getLdapConfig(localStorage.token);
  95. ENABLE_LDAP = ldapConfig.ENABLE_LDAP;
  96. });
  97. </script>
  98. <form
  99. class="flex flex-col h-full justify-between space-y-3 text-sm"
  100. on:submit|preventDefault={async () => {
  101. updateHandler();
  102. }}
  103. >
  104. <div class="mt-0.5 space-y-3 overflow-y-scroll scrollbar-hidden h-full">
  105. {#if adminConfig !== null}
  106. <div class="">
  107. <div class="mb-3.5">
  108. <div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
  109. <hr class=" border-gray-100 dark:border-gray-850 my-2" />
  110. <div class="mb-2.5">
  111. <div class=" mb-1 text-xs font-medium flex space-x-2 items-center">
  112. <div>
  113. {$i18n.t('Version')}
  114. </div>
  115. </div>
  116. <div class="flex w-full justify-between items-center">
  117. <div class="flex flex-col text-xs text-gray-700 dark:text-gray-200">
  118. <div class="flex gap-1">
  119. <Tooltip content={WEBUI_BUILD_HASH}>
  120. v{WEBUI_VERSION}
  121. </Tooltip>
  122. {#if $config?.features?.enable_version_update_check}
  123. <a
  124. href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
  125. target="_blank"
  126. >
  127. {updateAvailable === null
  128. ? $i18n.t('Checking for updates...')
  129. : updateAvailable
  130. ? `(v${version.latest} ${$i18n.t('available!')})`
  131. : $i18n.t('(latest)')}
  132. </a>
  133. {/if}
  134. </div>
  135. <button
  136. class=" underline flex items-center space-x-1 text-xs text-gray-500 dark:text-gray-500"
  137. type="button"
  138. on:click={() => {
  139. showChangelog.set(true);
  140. }}
  141. >
  142. <div>{$i18n.t("See what's new")}</div>
  143. </button>
  144. </div>
  145. {#if $config?.features?.enable_version_update_check}
  146. <button
  147. class=" text-xs px-3 py-1.5 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-medium"
  148. type="button"
  149. on:click={() => {
  150. checkForVersionUpdates();
  151. }}
  152. >
  153. {$i18n.t('Check for updates')}
  154. </button>
  155. {/if}
  156. </div>
  157. </div>
  158. <div class="mb-2.5">
  159. <div class="flex w-full justify-between items-center">
  160. <div class="text-xs pr-2">
  161. <div class="">
  162. {$i18n.t('Help')}
  163. </div>
  164. <div class=" text-xs text-gray-500">
  165. {$i18n.t('Discover how to use Open WebUI and seek support from the community.')}
  166. </div>
  167. </div>
  168. <a
  169. class="flex-shrink-0 text-xs font-medium underline"
  170. href="https://docs.openwebui.com/"
  171. target="_blank"
  172. >
  173. {$i18n.t('Documentation')}
  174. </a>
  175. </div>
  176. <div class="mt-1">
  177. <div class="flex space-x-1">
  178. <a href="https://discord.gg/5rJgQTnV4s" target="_blank">
  179. <img
  180. alt="Discord"
  181. src="https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white"
  182. />
  183. </a>
  184. <a href="https://twitter.com/OpenWebUI" target="_blank">
  185. <img
  186. alt="X (formerly Twitter) Follow"
  187. src="https://img.shields.io/twitter/follow/OpenWebUI"
  188. />
  189. </a>
  190. <a href="https://github.com/open-webui/open-webui" target="_blank">
  191. <img
  192. alt="Github Repo"
  193. src="https://img.shields.io/github/stars/open-webui/open-webui?style=social&label=Star us on Github"
  194. />
  195. </a>
  196. </div>
  197. </div>
  198. </div>
  199. <div class="mb-2.5">
  200. <div class="flex w-full justify-between items-center">
  201. <div class="text-xs pr-2">
  202. <div class="">
  203. {$i18n.t('License')}
  204. </div>
  205. {#if $config?.license_metadata}
  206. <a
  207. href="https://docs.openwebui.com/enterprise"
  208. target="_blank"
  209. class="text-gray-500 mt-0.5"
  210. >
  211. <span class=" capitalize text-black dark:text-white"
  212. >{$config?.license_metadata?.type}
  213. license</span
  214. >
  215. registered to
  216. <span class=" capitalize text-black dark:text-white"
  217. >{$config?.license_metadata?.organization_name}</span
  218. >
  219. for
  220. <span class=" font-medium text-black dark:text-white"
  221. >{$config?.license_metadata?.seats ?? 'Unlimited'} users.</span
  222. >
  223. </a>
  224. {#if $config?.license_metadata?.html}
  225. <div class="mt-0.5">
  226. {@html DOMPurify.sanitize($config?.license_metadata?.html)}
  227. </div>
  228. {/if}
  229. {:else}
  230. <a
  231. class=" text-xs hover:underline"
  232. href="https://docs.openwebui.com/enterprise"
  233. target="_blank"
  234. >
  235. <span class="text-gray-500">
  236. {$i18n.t(
  237. 'Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.'
  238. )}
  239. </span>
  240. </a>
  241. {/if}
  242. </div>
  243. <!-- <button
  244. class="flex-shrink-0 text-xs px-3 py-1.5 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-medium"
  245. >
  246. {$i18n.t('Activate')}
  247. </button> -->
  248. </div>
  249. </div>
  250. </div>
  251. <div class="mb-3">
  252. <div class=" mb-2.5 text-base font-medium">{$i18n.t('Authentication')}</div>
  253. <hr class=" border-gray-100 dark:border-gray-850 my-2" />
  254. <div class=" mb-2.5 flex w-full justify-between">
  255. <div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div>
  256. <div class="flex items-center relative">
  257. <select
  258. class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
  259. bind:value={adminConfig.DEFAULT_USER_ROLE}
  260. placeholder="Select a role"
  261. >
  262. <option value="pending">{$i18n.t('pending')}</option>
  263. <option value="user">{$i18n.t('user')}</option>
  264. <option value="admin">{$i18n.t('admin')}</option>
  265. </select>
  266. </div>
  267. </div>
  268. <div class=" mb-2.5 flex w-full justify-between pr-2">
  269. <div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div>
  270. <Switch bind:state={adminConfig.ENABLE_SIGNUP} />
  271. </div>
  272. <div class="mb-2.5 flex w-full items-center justify-between pr-2">
  273. <div class=" self-center text-xs font-medium">
  274. {$i18n.t('Show Admin Details in Account Pending Overlay')}
  275. </div>
  276. <Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} />
  277. </div>
  278. <div class="mb-2.5">
  279. <div class=" self-center text-xs font-medium mb-2">
  280. {$i18n.t('Pending User Overlay Title')}
  281. </div>
  282. <Textarea
  283. placeholder={$i18n.t(
  284. 'Enter a title for the pending user info overlay. Leave empty for default.'
  285. )}
  286. bind:value={adminConfig.PENDING_USER_OVERLAY_TITLE}
  287. />
  288. </div>
  289. <div class="mb-2.5">
  290. <div class=" self-center text-xs font-medium mb-2">
  291. {$i18n.t('Pending User Overlay Content')}
  292. </div>
  293. <Textarea
  294. placeholder={$i18n.t(
  295. 'Enter content for the pending user info overlay. Leave empty for default.'
  296. )}
  297. bind:value={adminConfig.PENDING_USER_OVERLAY_CONTENT}
  298. />
  299. </div>
  300. <div class="mb-2.5 flex w-full justify-between pr-2">
  301. <div class=" self-center text-xs font-medium">{$i18n.t('Enable API Key')}</div>
  302. <Switch bind:state={adminConfig.ENABLE_API_KEY} />
  303. </div>
  304. {#if adminConfig?.ENABLE_API_KEY}
  305. <div class="mb-2.5 flex w-full justify-between pr-2">
  306. <div class=" self-center text-xs font-medium">
  307. {$i18n.t('API Key Endpoint Restrictions')}
  308. </div>
  309. <Switch bind:state={adminConfig.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS} />
  310. </div>
  311. {#if adminConfig?.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS}
  312. <div class=" flex w-full flex-col pr-2">
  313. <div class=" text-xs font-medium">
  314. {$i18n.t('Allowed Endpoints')}
  315. </div>
  316. <input
  317. class="w-full mt-1 rounded-lg text-sm dark:text-gray-300 bg-transparent outline-hidden"
  318. type="text"
  319. placeholder={`e.g.) /api/v1/messages, /api/v1/channels`}
  320. bind:value={adminConfig.API_KEY_ALLOWED_ENDPOINTS}
  321. />
  322. <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
  323. <!-- https://docs.openwebui.com/getting-started/advanced-topics/api-endpoints -->
  324. <a
  325. href="https://docs.openwebui.com/getting-started/api-endpoints"
  326. target="_blank"
  327. class=" text-gray-300 font-medium underline"
  328. >
  329. {$i18n.t('To learn more about available endpoints, visit our documentation.')}
  330. </a>
  331. </div>
  332. </div>
  333. {/if}
  334. {/if}
  335. <div class=" mb-2.5 w-full justify-between">
  336. <div class="flex w-full justify-between">
  337. <div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
  338. </div>
  339. <div class="flex mt-2 space-x-2">
  340. <input
  341. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  342. type="text"
  343. placeholder={`e.g.) "30m","1h", "10d". `}
  344. bind:value={adminConfig.JWT_EXPIRES_IN}
  345. />
  346. </div>
  347. <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
  348. {$i18n.t('Valid time units:')}
  349. <span class=" text-gray-300 font-medium"
  350. >{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span
  351. >
  352. </div>
  353. </div>
  354. <div class=" space-y-3">
  355. <div class="mt-2 space-y-2 pr-1.5">
  356. <div class="flex justify-between items-center text-sm">
  357. <div class=" font-medium">{$i18n.t('LDAP')}</div>
  358. <div class="mt-1">
  359. <Switch bind:state={ENABLE_LDAP} />
  360. </div>
  361. </div>
  362. {#if ENABLE_LDAP}
  363. <div class="flex flex-col gap-1">
  364. <div class="flex w-full gap-2">
  365. <div class="w-full">
  366. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  367. {$i18n.t('Label')}
  368. </div>
  369. <input
  370. class="w-full bg-transparent outline-hidden py-0.5"
  371. required
  372. placeholder={$i18n.t('Enter server label')}
  373. bind:value={LDAP_SERVER.label}
  374. />
  375. </div>
  376. <div class="w-full"></div>
  377. </div>
  378. <div class="flex w-full gap-2">
  379. <div class="w-full">
  380. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  381. {$i18n.t('Host')}
  382. </div>
  383. <input
  384. class="w-full bg-transparent outline-hidden py-0.5"
  385. required
  386. placeholder={$i18n.t('Enter server host')}
  387. bind:value={LDAP_SERVER.host}
  388. />
  389. </div>
  390. <div class="w-full">
  391. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  392. {$i18n.t('Port')}
  393. </div>
  394. <Tooltip
  395. placement="top-start"
  396. content={$i18n.t('Default to 389 or 636 if TLS is enabled')}
  397. className="w-full"
  398. >
  399. <input
  400. class="w-full bg-transparent outline-hidden py-0.5"
  401. type="number"
  402. placeholder={$i18n.t('Enter server port')}
  403. bind:value={LDAP_SERVER.port}
  404. />
  405. </Tooltip>
  406. </div>
  407. </div>
  408. <div class="flex w-full gap-2">
  409. <div class="w-full">
  410. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  411. {$i18n.t('Application DN')}
  412. </div>
  413. <Tooltip
  414. content={$i18n.t('The Application Account DN you bind with for search')}
  415. placement="top-start"
  416. >
  417. <input
  418. class="w-full bg-transparent outline-hidden py-0.5"
  419. required
  420. placeholder={$i18n.t('Enter Application DN')}
  421. bind:value={LDAP_SERVER.app_dn}
  422. />
  423. </Tooltip>
  424. </div>
  425. <div class="w-full">
  426. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  427. {$i18n.t('Application DN Password')}
  428. </div>
  429. <SensitiveInput
  430. placeholder={$i18n.t('Enter Application DN Password')}
  431. bind:value={LDAP_SERVER.app_dn_password}
  432. />
  433. </div>
  434. </div>
  435. <div class="flex w-full gap-2">
  436. <div class="w-full">
  437. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  438. {$i18n.t('Attribute for Mail')}
  439. </div>
  440. <Tooltip
  441. content={$i18n.t(
  442. 'The LDAP attribute that maps to the mail that users use to sign in.'
  443. )}
  444. placement="top-start"
  445. >
  446. <input
  447. class="w-full bg-transparent outline-hidden py-0.5"
  448. required
  449. placeholder={$i18n.t('Example: mail')}
  450. bind:value={LDAP_SERVER.attribute_for_mail}
  451. />
  452. </Tooltip>
  453. </div>
  454. </div>
  455. <div class="flex w-full gap-2">
  456. <div class="w-full">
  457. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  458. {$i18n.t('Attribute for Username')}
  459. </div>
  460. <Tooltip
  461. content={$i18n.t(
  462. 'The LDAP attribute that maps to the username that users use to sign in.'
  463. )}
  464. placement="top-start"
  465. >
  466. <input
  467. class="w-full bg-transparent outline-hidden py-0.5"
  468. required
  469. placeholder={$i18n.t(
  470. 'Example: sAMAccountName or uid or userPrincipalName'
  471. )}
  472. bind:value={LDAP_SERVER.attribute_for_username}
  473. />
  474. </Tooltip>
  475. </div>
  476. </div>
  477. <div class="flex w-full gap-2">
  478. <div class="w-full">
  479. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  480. {$i18n.t('Search Base')}
  481. </div>
  482. <Tooltip
  483. content={$i18n.t('The base to search for users')}
  484. placement="top-start"
  485. >
  486. <input
  487. class="w-full bg-transparent outline-hidden py-0.5"
  488. required
  489. placeholder={$i18n.t('Example: ou=users,dc=foo,dc=example')}
  490. bind:value={LDAP_SERVER.search_base}
  491. />
  492. </Tooltip>
  493. </div>
  494. </div>
  495. <div class="flex w-full gap-2">
  496. <div class="w-full">
  497. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  498. {$i18n.t('Search Filters')}
  499. </div>
  500. <input
  501. class="w-full bg-transparent outline-hidden py-0.5"
  502. placeholder={$i18n.t('Example: (&(objectClass=inetOrgPerson)(uid=%s))')}
  503. bind:value={LDAP_SERVER.search_filters}
  504. />
  505. </div>
  506. </div>
  507. <div class="text-xs text-gray-400 dark:text-gray-500">
  508. <a
  509. class=" text-gray-300 font-medium underline"
  510. href="https://ldap.com/ldap-filters/"
  511. target="_blank"
  512. >
  513. {$i18n.t('Click here for filter guides.')}
  514. </a>
  515. </div>
  516. <div>
  517. <div class="flex justify-between items-center text-sm">
  518. <div class=" font-medium">{$i18n.t('TLS')}</div>
  519. <div class="mt-1">
  520. <Switch bind:state={LDAP_SERVER.use_tls} />
  521. </div>
  522. </div>
  523. {#if LDAP_SERVER.use_tls}
  524. <div class="flex w-full gap-2">
  525. <div class="w-full">
  526. <div class=" self-center text-xs font-medium min-w-fit mb-1 mt-1">
  527. {$i18n.t('Certificate Path')}
  528. </div>
  529. <input
  530. class="w-full bg-transparent outline-hidden py-0.5"
  531. placeholder={$i18n.t('Enter certificate path')}
  532. bind:value={LDAP_SERVER.certificate_path}
  533. />
  534. </div>
  535. </div>
  536. <div class="flex justify-between items-center text-xs">
  537. <div class=" font-medium">Validate certificate</div>
  538. <div class="mt-1">
  539. <Switch bind:state={LDAP_SERVER.validate_cert} />
  540. </div>
  541. </div>
  542. <div class="flex w-full gap-2">
  543. <div class="w-full">
  544. <div class=" self-center text-xs font-medium min-w-fit mb-1">
  545. {$i18n.t('Ciphers')}
  546. </div>
  547. <Tooltip content={$i18n.t('Default to ALL')} placement="top-start">
  548. <input
  549. class="w-full bg-transparent outline-hidden py-0.5"
  550. placeholder={$i18n.t('Example: ALL')}
  551. bind:value={LDAP_SERVER.ciphers}
  552. />
  553. </Tooltip>
  554. </div>
  555. <div class="w-full"></div>
  556. </div>
  557. {/if}
  558. </div>
  559. </div>
  560. {/if}
  561. </div>
  562. </div>
  563. </div>
  564. <div class="mb-3">
  565. <div class=" mb-2.5 text-base font-medium">{$i18n.t('Features')}</div>
  566. <hr class=" border-gray-100 dark:border-gray-850 my-2" />
  567. <div class="mb-2.5 flex w-full items-center justify-between pr-2">
  568. <div class=" self-center text-xs font-medium">
  569. {$i18n.t('Enable Community Sharing')}
  570. </div>
  571. <Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} />
  572. </div>
  573. <div class="mb-2.5 flex w-full items-center justify-between pr-2">
  574. <div class=" self-center text-xs font-medium">{$i18n.t('Enable Message Rating')}</div>
  575. <Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
  576. </div>
  577. <div class="mb-2.5 flex w-full items-center justify-between pr-2">
  578. <div class=" self-center text-xs font-medium">
  579. {$i18n.t('Notes')} ({$i18n.t('Beta')})
  580. </div>
  581. <Switch bind:state={adminConfig.ENABLE_NOTES} />
  582. </div>
  583. <div class="mb-2.5 flex w-full items-center justify-between pr-2">
  584. <div class=" self-center text-xs font-medium">
  585. {$i18n.t('Channels')} ({$i18n.t('Beta')})
  586. </div>
  587. <Switch bind:state={adminConfig.ENABLE_CHANNELS} />
  588. </div>
  589. <div class="mb-2.5 flex w-full items-center justify-between pr-2">
  590. <div class=" self-center text-xs font-medium">
  591. {$i18n.t('User Webhooks')}
  592. </div>
  593. <Switch bind:state={adminConfig.ENABLE_USER_WEBHOOKS} />
  594. </div>
  595. <div class="mb-2.5">
  596. <div class=" self-center text-xs font-medium mb-2">
  597. {$i18n.t('Response Watermark')}
  598. </div>
  599. <Textarea
  600. placeholder={$i18n.t('Enter a watermark for the response. Leave empty for none.')}
  601. bind:value={adminConfig.RESPONSE_WATERMARK}
  602. />
  603. </div>
  604. <div class="mb-2.5 w-full justify-between">
  605. <div class="flex w-full justify-between">
  606. <div class=" self-center text-xs font-medium">{$i18n.t('WebUI URL')}</div>
  607. </div>
  608. <div class="flex mt-2 space-x-2">
  609. <input
  610. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  611. type="text"
  612. placeholder={`e.g.) "http://localhost:3000"`}
  613. bind:value={adminConfig.WEBUI_URL}
  614. />
  615. </div>
  616. <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
  617. {$i18n.t(
  618. 'Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.'
  619. )}
  620. </div>
  621. </div>
  622. <div class=" w-full justify-between">
  623. <div class="flex w-full justify-between">
  624. <div class=" self-center text-xs font-medium">{$i18n.t('Webhook URL')}</div>
  625. </div>
  626. <div class="flex mt-2 space-x-2">
  627. <input
  628. class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
  629. type="text"
  630. placeholder={`https://example.com/webhook`}
  631. bind:value={webhookUrl}
  632. />
  633. </div>
  634. </div>
  635. </div>
  636. </div>
  637. {/if}
  638. </div>
  639. <div class="flex justify-end pt-3 text-sm font-medium">
  640. <button
  641. class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
  642. type="submit"
  643. >
  644. {$i18n.t('Save')}
  645. </button>
  646. </div>
  647. </form>