SystemSetting.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. import React, { useEffect, useState } from 'react';
  2. import {
  3. Button,
  4. Divider,
  5. Form,
  6. Grid,
  7. Header,
  8. Message,
  9. Modal,
  10. } from 'semantic-ui-react';
  11. import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers';
  12. const SystemSetting = () => {
  13. let [inputs, setInputs] = useState({
  14. PasswordLoginEnabled: '',
  15. PasswordRegisterEnabled: '',
  16. EmailVerificationEnabled: '',
  17. GitHubOAuthEnabled: '',
  18. GitHubClientId: '',
  19. GitHubClientSecret: '',
  20. Notice: '',
  21. SMTPServer: '',
  22. SMTPPort: '',
  23. SMTPAccount: '',
  24. SMTPFrom: '',
  25. SMTPToken: '',
  26. ServerAddress: '',
  27. EpayId: '',
  28. EpayKey: '',
  29. Price: 7.3,
  30. MinTopUp: 1,
  31. TopupGroupRatio: '',
  32. PayAddress: '',
  33. CustomCallbackAddress: '',
  34. Footer: '',
  35. WeChatAuthEnabled: '',
  36. WeChatServerAddress: '',
  37. WeChatServerToken: '',
  38. WeChatAccountQRCodeImageURL: '',
  39. TurnstileCheckEnabled: '',
  40. TurnstileSiteKey: '',
  41. TurnstileSecretKey: '',
  42. RegisterEnabled: '',
  43. EmailDomainRestrictionEnabled: '',
  44. EmailDomainWhitelist: '',
  45. // telegram login
  46. TelegramOAuthEnabled: '',
  47. TelegramBotToken: '',
  48. TelegramBotName: '',
  49. });
  50. const [originInputs, setOriginInputs] = useState({});
  51. let [loading, setLoading] = useState(false);
  52. const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]);
  53. const [restrictedDomainInput, setRestrictedDomainInput] = useState('');
  54. const [showPasswordWarningModal, setShowPasswordWarningModal] =
  55. useState(false);
  56. const getOptions = async () => {
  57. const res = await API.get('/api/option/');
  58. const { success, message, data } = res.data;
  59. if (success) {
  60. let newInputs = {};
  61. data.forEach((item) => {
  62. if (item.key === 'TopupGroupRatio') {
  63. item.value = JSON.stringify(JSON.parse(item.value), null, 2);
  64. }
  65. newInputs[item.key] = item.value;
  66. });
  67. setInputs({
  68. ...newInputs,
  69. EmailDomainWhitelist: newInputs.EmailDomainWhitelist.split(','),
  70. });
  71. setOriginInputs(newInputs);
  72. setEmailDomainWhitelist(
  73. newInputs.EmailDomainWhitelist.split(',').map((item) => {
  74. return { key: item, text: item, value: item };
  75. }),
  76. );
  77. } else {
  78. showError(message);
  79. }
  80. };
  81. useEffect(() => {
  82. getOptions().then();
  83. }, []);
  84. const updateOption = async (key, value) => {
  85. setLoading(true);
  86. switch (key) {
  87. case 'PasswordLoginEnabled':
  88. case 'PasswordRegisterEnabled':
  89. case 'EmailVerificationEnabled':
  90. case 'GitHubOAuthEnabled':
  91. case 'WeChatAuthEnabled':
  92. case 'TelegramOAuthEnabled':
  93. case 'TurnstileCheckEnabled':
  94. case 'EmailDomainRestrictionEnabled':
  95. case 'RegisterEnabled':
  96. value = inputs[key] === 'true' ? 'false' : 'true';
  97. break;
  98. default:
  99. break;
  100. }
  101. const res = await API.put('/api/option/', {
  102. key,
  103. value,
  104. });
  105. const { success, message } = res.data;
  106. if (success) {
  107. if (key === 'EmailDomainWhitelist') {
  108. value = value.split(',');
  109. }
  110. if (key === 'Price') {
  111. value = parseFloat(value);
  112. }
  113. setInputs((inputs) => ({
  114. ...inputs,
  115. [key]: value,
  116. }));
  117. } else {
  118. showError(message);
  119. }
  120. setLoading(false);
  121. };
  122. const handleInputChange = async (e, { name, value }) => {
  123. if (name === 'PasswordLoginEnabled' && inputs[name] === 'true') {
  124. // block disabling password login
  125. setShowPasswordWarningModal(true);
  126. return;
  127. }
  128. if (
  129. name === 'Notice' ||
  130. name.startsWith('SMTP') ||
  131. name === 'ServerAddress' ||
  132. name === 'EpayId' ||
  133. name === 'EpayKey' ||
  134. name === 'Price' ||
  135. name === 'PayAddress' ||
  136. name === 'GitHubClientId' ||
  137. name === 'GitHubClientSecret' ||
  138. name === 'WeChatServerAddress' ||
  139. name === 'WeChatServerToken' ||
  140. name === 'WeChatAccountQRCodeImageURL' ||
  141. name === 'TurnstileSiteKey' ||
  142. name === 'TurnstileSecretKey' ||
  143. name === 'EmailDomainWhitelist' ||
  144. name === 'TopupGroupRatio' ||
  145. name === 'TelegramBotToken' ||
  146. name === 'TelegramBotName'
  147. ) {
  148. setInputs((inputs) => ({ ...inputs, [name]: value }));
  149. } else {
  150. await updateOption(name, value);
  151. }
  152. };
  153. const submitServerAddress = async () => {
  154. let ServerAddress = removeTrailingSlash(inputs.ServerAddress);
  155. await updateOption('ServerAddress', ServerAddress);
  156. };
  157. const submitPayAddress = async () => {
  158. if (inputs.ServerAddress === '') {
  159. showError('请先填写服务器地址');
  160. return;
  161. }
  162. if (originInputs['TopupGroupRatio'] !== inputs.TopupGroupRatio) {
  163. if (!verifyJSON(inputs.TopupGroupRatio)) {
  164. showError('充值分组倍率不是合法的 JSON 字符串');
  165. return;
  166. }
  167. await updateOption('TopupGroupRatio', inputs.TopupGroupRatio);
  168. }
  169. let PayAddress = removeTrailingSlash(inputs.PayAddress);
  170. await updateOption('PayAddress', PayAddress);
  171. if (inputs.EpayId !== '') {
  172. await updateOption('EpayId', inputs.EpayId);
  173. }
  174. if (inputs.EpayKey !== '') {
  175. await updateOption('EpayKey', inputs.EpayKey);
  176. }
  177. await updateOption('Price', '' + inputs.Price);
  178. };
  179. const submitSMTP = async () => {
  180. if (originInputs['SMTPServer'] !== inputs.SMTPServer) {
  181. await updateOption('SMTPServer', inputs.SMTPServer);
  182. }
  183. if (originInputs['SMTPAccount'] !== inputs.SMTPAccount) {
  184. await updateOption('SMTPAccount', inputs.SMTPAccount);
  185. }
  186. if (originInputs['SMTPFrom'] !== inputs.SMTPFrom) {
  187. await updateOption('SMTPFrom', inputs.SMTPFrom);
  188. }
  189. if (
  190. originInputs['SMTPPort'] !== inputs.SMTPPort &&
  191. inputs.SMTPPort !== ''
  192. ) {
  193. await updateOption('SMTPPort', inputs.SMTPPort);
  194. }
  195. if (
  196. originInputs['SMTPToken'] !== inputs.SMTPToken &&
  197. inputs.SMTPToken !== ''
  198. ) {
  199. await updateOption('SMTPToken', inputs.SMTPToken);
  200. }
  201. };
  202. const submitEmailDomainWhitelist = async () => {
  203. if (
  204. originInputs['EmailDomainWhitelist'] !==
  205. inputs.EmailDomainWhitelist.join(',') &&
  206. inputs.SMTPToken !== ''
  207. ) {
  208. await updateOption(
  209. 'EmailDomainWhitelist',
  210. inputs.EmailDomainWhitelist.join(','),
  211. );
  212. }
  213. };
  214. const submitWeChat = async () => {
  215. if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) {
  216. await updateOption(
  217. 'WeChatServerAddress',
  218. removeTrailingSlash(inputs.WeChatServerAddress),
  219. );
  220. }
  221. if (
  222. originInputs['WeChatAccountQRCodeImageURL'] !==
  223. inputs.WeChatAccountQRCodeImageURL
  224. ) {
  225. await updateOption(
  226. 'WeChatAccountQRCodeImageURL',
  227. inputs.WeChatAccountQRCodeImageURL,
  228. );
  229. }
  230. if (
  231. originInputs['WeChatServerToken'] !== inputs.WeChatServerToken &&
  232. inputs.WeChatServerToken !== ''
  233. ) {
  234. await updateOption('WeChatServerToken', inputs.WeChatServerToken);
  235. }
  236. };
  237. const submitGitHubOAuth = async () => {
  238. if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) {
  239. await updateOption('GitHubClientId', inputs.GitHubClientId);
  240. }
  241. if (
  242. originInputs['GitHubClientSecret'] !== inputs.GitHubClientSecret &&
  243. inputs.GitHubClientSecret !== ''
  244. ) {
  245. await updateOption('GitHubClientSecret', inputs.GitHubClientSecret);
  246. }
  247. };
  248. const submitTelegramSettings = async () => {
  249. // await updateOption('TelegramOAuthEnabled', inputs.TelegramOAuthEnabled);
  250. await updateOption('TelegramBotToken', inputs.TelegramBotToken);
  251. await updateOption('TelegramBotName', inputs.TelegramBotName);
  252. };
  253. const submitTurnstile = async () => {
  254. if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) {
  255. await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey);
  256. }
  257. if (
  258. originInputs['TurnstileSecretKey'] !== inputs.TurnstileSecretKey &&
  259. inputs.TurnstileSecretKey !== ''
  260. ) {
  261. await updateOption('TurnstileSecretKey', inputs.TurnstileSecretKey);
  262. }
  263. };
  264. const submitNewRestrictedDomain = () => {
  265. const localDomainList = inputs.EmailDomainWhitelist;
  266. if (
  267. restrictedDomainInput !== '' &&
  268. !localDomainList.includes(restrictedDomainInput)
  269. ) {
  270. setRestrictedDomainInput('');
  271. setInputs({
  272. ...inputs,
  273. EmailDomainWhitelist: [...localDomainList, restrictedDomainInput],
  274. });
  275. setEmailDomainWhitelist([
  276. ...EmailDomainWhitelist,
  277. {
  278. key: restrictedDomainInput,
  279. text: restrictedDomainInput,
  280. value: restrictedDomainInput,
  281. },
  282. ]);
  283. }
  284. };
  285. return (
  286. <Grid columns={1}>
  287. <Grid.Column>
  288. <Form loading={loading}>
  289. <Header as='h3'>通用设置</Header>
  290. <Form.Group widths='equal'>
  291. <Form.Input
  292. label='服务器地址'
  293. placeholder='例如:https://yourdomain.com'
  294. value={inputs.ServerAddress}
  295. name='ServerAddress'
  296. onChange={handleInputChange}
  297. />
  298. </Form.Group>
  299. <Form.Button onClick={submitServerAddress}>
  300. 更新服务器地址
  301. </Form.Button>
  302. <Divider />
  303. <Header as='h3'>
  304. 支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
  305. </Header>
  306. <Form.Group widths='equal'>
  307. <Form.Input
  308. label='支付地址,不填写则不启用在线支付'
  309. placeholder='例如:https://yourdomain.com'
  310. value={inputs.PayAddress}
  311. name='PayAddress'
  312. onChange={handleInputChange}
  313. />
  314. <Form.Input
  315. label='易支付商户ID'
  316. placeholder='例如:0001'
  317. value={inputs.EpayId}
  318. name='EpayId'
  319. onChange={handleInputChange}
  320. />
  321. <Form.Input
  322. label='易支付商户密钥'
  323. placeholder='例如:dejhfueqhujasjmndbjkqaw'
  324. value={inputs.EpayKey}
  325. name='EpayKey'
  326. onChange={handleInputChange}
  327. />
  328. </Form.Group>
  329. <Form.Group widths='equal'>
  330. <Form.Input
  331. label='回调地址,不填写则使用上方服务器地址作为回调地址'
  332. placeholder='例如:https://yourdomain.com'
  333. value={inputs.CustomCallbackAddress}
  334. name='CustomCallbackAddress'
  335. onChange={handleInputChange}
  336. />
  337. <Form.Input
  338. label='充值价格(x元/美金)'
  339. placeholder='例如:7,就是7元/美金'
  340. value={inputs.Price}
  341. name='Price'
  342. min={0}
  343. onChange={handleInputChange}
  344. />
  345. <Form.Input
  346. label='最低充值数量'
  347. placeholder='例如:2,就是最低充值2$'
  348. value={inputs.MinTopUp}
  349. name='MinTopUp'
  350. min={1}
  351. onChange={handleInputChange}
  352. />
  353. </Form.Group>
  354. <Form.Group widths='equal'>
  355. <Form.TextArea
  356. label='充值分组倍率'
  357. name='TopupGroupRatio'
  358. onChange={handleInputChange}
  359. style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
  360. autoComplete='new-password'
  361. value={inputs.TopupGroupRatio}
  362. placeholder='为一个 JSON 文本,键为组名称,值为倍率'
  363. />
  364. </Form.Group>
  365. <Form.Button onClick={submitPayAddress}>更新支付设置</Form.Button>
  366. <Divider />
  367. <Header as='h3'>配置登录注册</Header>
  368. <Form.Group inline>
  369. <Form.Checkbox
  370. checked={inputs.PasswordLoginEnabled === 'true'}
  371. label='允许通过密码进行登录'
  372. name='PasswordLoginEnabled'
  373. onChange={handleInputChange}
  374. />
  375. {showPasswordWarningModal && (
  376. <Modal
  377. open={showPasswordWarningModal}
  378. onClose={() => setShowPasswordWarningModal(false)}
  379. size={'tiny'}
  380. style={{ maxWidth: '450px' }}
  381. >
  382. <Modal.Header>警告</Modal.Header>
  383. <Modal.Content>
  384. <p>
  385. 取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?
  386. </p>
  387. </Modal.Content>
  388. <Modal.Actions>
  389. <Button onClick={() => setShowPasswordWarningModal(false)}>
  390. 取消
  391. </Button>
  392. <Button
  393. color='yellow'
  394. onClick={async () => {
  395. setShowPasswordWarningModal(false);
  396. await updateOption('PasswordLoginEnabled', 'false');
  397. }}
  398. >
  399. 确定
  400. </Button>
  401. </Modal.Actions>
  402. </Modal>
  403. )}
  404. <Form.Checkbox
  405. checked={inputs.PasswordRegisterEnabled === 'true'}
  406. label='允许通过密码进行注册'
  407. name='PasswordRegisterEnabled'
  408. onChange={handleInputChange}
  409. />
  410. <Form.Checkbox
  411. checked={inputs.EmailVerificationEnabled === 'true'}
  412. label='通过密码注册时需要进行邮箱验证'
  413. name='EmailVerificationEnabled'
  414. onChange={handleInputChange}
  415. />
  416. <Form.Checkbox
  417. checked={inputs.GitHubOAuthEnabled === 'true'}
  418. label='允许通过 GitHub 账户登录 & 注册'
  419. name='GitHubOAuthEnabled'
  420. onChange={handleInputChange}
  421. />
  422. <Form.Checkbox
  423. checked={inputs.WeChatAuthEnabled === 'true'}
  424. label='允许通过微信登录 & 注册'
  425. name='WeChatAuthEnabled'
  426. onChange={handleInputChange}
  427. />
  428. <Form.Checkbox
  429. checked={inputs.TelegramOAuthEnabled === 'true'}
  430. label='允许通过 Telegram 进行登录'
  431. name='TelegramOAuthEnabled'
  432. onChange={handleInputChange}
  433. />
  434. </Form.Group>
  435. <Form.Group inline>
  436. <Form.Checkbox
  437. checked={inputs.RegisterEnabled === 'true'}
  438. label='允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)'
  439. name='RegisterEnabled'
  440. onChange={handleInputChange}
  441. />
  442. <Form.Checkbox
  443. checked={inputs.TurnstileCheckEnabled === 'true'}
  444. label='启用 Turnstile 用户校验'
  445. name='TurnstileCheckEnabled'
  446. onChange={handleInputChange}
  447. />
  448. </Form.Group>
  449. <Divider />
  450. <Header as='h3'>
  451. 配置邮箱域名白名单
  452. <Header.Subheader>
  453. 用以防止恶意用户利用临时邮箱批量注册
  454. </Header.Subheader>
  455. </Header>
  456. <Form.Group widths={3}>
  457. <Form.Checkbox
  458. label='启用邮箱域名白名单'
  459. name='EmailDomainRestrictionEnabled'
  460. onChange={handleInputChange}
  461. checked={inputs.EmailDomainRestrictionEnabled === 'true'}
  462. />
  463. </Form.Group>
  464. <Form.Group widths={2}>
  465. <Form.Dropdown
  466. label='允许的邮箱域名'
  467. placeholder='允许的邮箱域名'
  468. name='EmailDomainWhitelist'
  469. required
  470. fluid
  471. multiple
  472. selection
  473. onChange={handleInputChange}
  474. value={inputs.EmailDomainWhitelist}
  475. autoComplete='new-password'
  476. options={EmailDomainWhitelist}
  477. />
  478. <Form.Input
  479. label='添加新的允许的邮箱域名'
  480. action={
  481. <Button
  482. type='button'
  483. onClick={() => {
  484. submitNewRestrictedDomain();
  485. }}
  486. >
  487. 填入
  488. </Button>
  489. }
  490. onKeyDown={(e) => {
  491. if (e.key === 'Enter') {
  492. submitNewRestrictedDomain();
  493. }
  494. }}
  495. autoComplete='new-password'
  496. placeholder='输入新的允许的邮箱域名'
  497. value={restrictedDomainInput}
  498. onChange={(e, { value }) => {
  499. setRestrictedDomainInput(value);
  500. }}
  501. />
  502. </Form.Group>
  503. <Form.Button onClick={submitEmailDomainWhitelist}>
  504. 保存邮箱域名白名单设置
  505. </Form.Button>
  506. <Divider />
  507. <Header as='h3'>
  508. 配置 SMTP
  509. <Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
  510. </Header>
  511. <Form.Group widths={3}>
  512. <Form.Input
  513. label='SMTP 服务器地址'
  514. name='SMTPServer'
  515. onChange={handleInputChange}
  516. autoComplete='new-password'
  517. value={inputs.SMTPServer}
  518. placeholder='例如:smtp.qq.com'
  519. />
  520. <Form.Input
  521. label='SMTP 端口'
  522. name='SMTPPort'
  523. onChange={handleInputChange}
  524. autoComplete='new-password'
  525. value={inputs.SMTPPort}
  526. placeholder='默认: 587'
  527. />
  528. <Form.Input
  529. label='SMTP 账户'
  530. name='SMTPAccount'
  531. onChange={handleInputChange}
  532. autoComplete='new-password'
  533. value={inputs.SMTPAccount}
  534. placeholder='通常是邮箱地址'
  535. />
  536. </Form.Group>
  537. <Form.Group widths={3}>
  538. <Form.Input
  539. label='SMTP 发送者邮箱'
  540. name='SMTPFrom'
  541. onChange={handleInputChange}
  542. autoComplete='new-password'
  543. value={inputs.SMTPFrom}
  544. placeholder='通常和邮箱地址保持一致'
  545. />
  546. <Form.Input
  547. label='SMTP 访问凭证'
  548. name='SMTPToken'
  549. onChange={handleInputChange}
  550. type='password'
  551. autoComplete='new-password'
  552. checked={inputs.RegisterEnabled === 'true'}
  553. placeholder='敏感信息不会发送到前端显示'
  554. />
  555. </Form.Group>
  556. <Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
  557. <Divider />
  558. <Header as='h3'>
  559. 配置 GitHub OAuth App
  560. <Header.Subheader>
  561. 用以支持通过 GitHub 进行登录注册,
  562. <a
  563. href='https://github.com/settings/developers'
  564. target='_blank'
  565. rel='noreferrer'
  566. >
  567. 点击此处
  568. </a>
  569. 管理你的 GitHub OAuth App
  570. </Header.Subheader>
  571. </Header>
  572. <Message>
  573. Homepage URL 填 <code>{inputs.ServerAddress}</code>
  574. ,Authorization callback URL 填{' '}
  575. <code>{`${inputs.ServerAddress}/oauth/github`}</code>
  576. </Message>
  577. <Form.Group widths={3}>
  578. <Form.Input
  579. label='GitHub Client ID'
  580. name='GitHubClientId'
  581. onChange={handleInputChange}
  582. autoComplete='new-password'
  583. value={inputs.GitHubClientId}
  584. placeholder='输入你注册的 GitHub OAuth APP 的 ID'
  585. />
  586. <Form.Input
  587. label='GitHub Client Secret'
  588. name='GitHubClientSecret'
  589. onChange={handleInputChange}
  590. type='password'
  591. autoComplete='new-password'
  592. value={inputs.GitHubClientSecret}
  593. placeholder='敏感信息不会发送到前端显示'
  594. />
  595. </Form.Group>
  596. <Form.Button onClick={submitGitHubOAuth}>
  597. 保存 GitHub OAuth 设置
  598. </Form.Button>
  599. <Divider />
  600. <Header as='h3'>
  601. 配置 WeChat Server
  602. <Header.Subheader>
  603. 用以支持通过微信进行登录注册,
  604. <a
  605. href='https://github.com/songquanpeng/wechat-server'
  606. target='_blank'
  607. rel='noreferrer'
  608. >
  609. 点击此处
  610. </a>
  611. 了解 WeChat Server
  612. </Header.Subheader>
  613. </Header>
  614. <Form.Group widths={3}>
  615. <Form.Input
  616. label='WeChat Server 服务器地址'
  617. name='WeChatServerAddress'
  618. placeholder='例如:https://yourdomain.com'
  619. onChange={handleInputChange}
  620. autoComplete='new-password'
  621. value={inputs.WeChatServerAddress}
  622. />
  623. <Form.Input
  624. label='WeChat Server 访问凭证'
  625. name='WeChatServerToken'
  626. type='password'
  627. onChange={handleInputChange}
  628. autoComplete='new-password'
  629. value={inputs.WeChatServerToken}
  630. placeholder='敏感信息不会发送到前端显示'
  631. />
  632. <Form.Input
  633. label='微信公众号二维码图片链接'
  634. name='WeChatAccountQRCodeImageURL'
  635. onChange={handleInputChange}
  636. autoComplete='new-password'
  637. value={inputs.WeChatAccountQRCodeImageURL}
  638. placeholder='输入一个图片链接'
  639. />
  640. </Form.Group>
  641. <Form.Button onClick={submitWeChat}>
  642. 保存 WeChat Server 设置
  643. </Form.Button>
  644. <Divider />
  645. <Header as='h3'>配置 Telegram 登录</Header>
  646. <Form.Group inline>
  647. <Form.Input
  648. label='Telegram Bot Token'
  649. name='TelegramBotToken'
  650. onChange={handleInputChange}
  651. value={inputs.TelegramBotToken}
  652. placeholder='输入你的 Telegram Bot Token'
  653. />
  654. <Form.Input
  655. label='Telegram Bot 名称'
  656. name='TelegramBotName'
  657. onChange={handleInputChange}
  658. value={inputs.TelegramBotName}
  659. placeholder='输入你的 Telegram Bot 名称'
  660. />
  661. </Form.Group>
  662. <Form.Button onClick={submitTelegramSettings}>
  663. 保存 Telegram 登录设置
  664. </Form.Button>
  665. <Divider />
  666. <Header as='h3'>
  667. 配置 Turnstile
  668. <Header.Subheader>
  669. 用以支持用户校验,
  670. <a
  671. href='https://dash.cloudflare.com/'
  672. target='_blank'
  673. rel='noreferrer'
  674. >
  675. 点击此处
  676. </a>
  677. 管理你的 Turnstile Sites,推荐选择 Invisible Widget Type
  678. </Header.Subheader>
  679. </Header>
  680. <Form.Group widths={3}>
  681. <Form.Input
  682. label='Turnstile Site Key'
  683. name='TurnstileSiteKey'
  684. onChange={handleInputChange}
  685. autoComplete='new-password'
  686. value={inputs.TurnstileSiteKey}
  687. placeholder='输入你注册的 Turnstile Site Key'
  688. />
  689. <Form.Input
  690. label='Turnstile Secret Key'
  691. name='TurnstileSecretKey'
  692. onChange={handleInputChange}
  693. type='password'
  694. autoComplete='new-password'
  695. value={inputs.TurnstileSecretKey}
  696. placeholder='敏感信息不会发送到前端显示'
  697. />
  698. </Form.Group>
  699. <Form.Button onClick={submitTurnstile}>
  700. 保存 Turnstile 设置
  701. </Form.Button>
  702. </Form>
  703. </Grid.Column>
  704. </Grid>
  705. );
  706. };
  707. export default SystemSetting;