SystemSetting.js 24 KB

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