SafetySetting.js 26 KB

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