SystemSetting.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. import React, { useEffect, useState } from 'react';
  2. import { Divider, Form, Grid, Header, Message } from 'semantic-ui-react';
  3. import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers';
  4. const SystemSetting = () => {
  5. let [inputs, setInputs] = useState({
  6. PasswordLoginEnabled: '',
  7. PasswordRegisterEnabled: '',
  8. EmailVerificationEnabled: '',
  9. GitHubOAuthEnabled: '',
  10. GitHubClientId: '',
  11. GitHubClientSecret: '',
  12. Notice: '',
  13. SMTPServer: '',
  14. SMTPPort: '',
  15. SMTPAccount: '',
  16. SMTPFrom: '',
  17. SMTPToken: '',
  18. ServerAddress: '',
  19. Footer: '',
  20. WeChatAuthEnabled: '',
  21. WeChatServerAddress: '',
  22. WeChatServerToken: '',
  23. WeChatAccountQRCodeImageURL: '',
  24. TurnstileCheckEnabled: '',
  25. TurnstileSiteKey: '',
  26. TurnstileSecretKey: '',
  27. RegisterEnabled: '',
  28. QuotaForNewUser: 0,
  29. ModelRatio: '',
  30. TopUpLink: '',
  31. AutomaticDisableChannelEnabled: '',
  32. ChannelDisableThreshold: 0,
  33. });
  34. let originInputs = {};
  35. let [loading, setLoading] = useState(false);
  36. const getOptions = async () => {
  37. const res = await API.get('/api/option');
  38. const { success, message, data } = res.data;
  39. if (success) {
  40. let newInputs = {};
  41. data.forEach((item) => {
  42. newInputs[item.key] = item.value;
  43. });
  44. setInputs(newInputs);
  45. originInputs = newInputs;
  46. } else {
  47. showError(message);
  48. }
  49. };
  50. useEffect(() => {
  51. getOptions().then();
  52. }, []);
  53. const updateOption = async (key, value) => {
  54. setLoading(true);
  55. switch (key) {
  56. case 'PasswordLoginEnabled':
  57. case 'PasswordRegisterEnabled':
  58. case 'EmailVerificationEnabled':
  59. case 'GitHubOAuthEnabled':
  60. case 'WeChatAuthEnabled':
  61. case 'TurnstileCheckEnabled':
  62. case 'RegisterEnabled':
  63. case 'AutomaticDisableChannelEnabled':
  64. value = inputs[key] === 'true' ? 'false' : 'true';
  65. break;
  66. default:
  67. break;
  68. }
  69. const res = await API.put('/api/option', {
  70. key,
  71. value
  72. });
  73. const { success, message } = res.data;
  74. if (success) {
  75. setInputs((inputs) => ({ ...inputs, [key]: value }));
  76. } else {
  77. showError(message);
  78. }
  79. setLoading(false);
  80. };
  81. const handleInputChange = async (e, { name, value }) => {
  82. if (
  83. name === 'Notice' ||
  84. name.startsWith('SMTP') ||
  85. name === 'ServerAddress' ||
  86. name === 'GitHubClientId' ||
  87. name === 'GitHubClientSecret' ||
  88. name === 'WeChatServerAddress' ||
  89. name === 'WeChatServerToken' ||
  90. name === 'WeChatAccountQRCodeImageURL' ||
  91. name === 'TurnstileSiteKey' ||
  92. name === 'TurnstileSecretKey' ||
  93. name === 'QuotaForNewUser' ||
  94. name === 'ModelRatio' ||
  95. name === 'TopUpLink'
  96. ) {
  97. setInputs((inputs) => ({ ...inputs, [name]: value }));
  98. } else {
  99. await updateOption(name, value);
  100. }
  101. };
  102. const submitServerAddress = async () => {
  103. let ServerAddress = removeTrailingSlash(inputs.ServerAddress);
  104. await updateOption('ServerAddress', ServerAddress);
  105. };
  106. const submitOperationConfig = async () => {
  107. if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) {
  108. await updateOption('QuotaForNewUser', inputs.QuotaForNewUser);
  109. }
  110. if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
  111. if (!verifyJSON(inputs.ModelRatio)) {
  112. showError('模型倍率不是合法的 JSON 字符串');
  113. return;
  114. }
  115. await updateOption('ModelRatio', inputs.ModelRatio);
  116. }
  117. if (originInputs['TopUpLink'] !== inputs.TopUpLink) {
  118. await updateOption('TopUpLink', inputs.TopUpLink);
  119. }
  120. };
  121. const submitSMTP = async () => {
  122. if (originInputs['SMTPServer'] !== inputs.SMTPServer) {
  123. await updateOption('SMTPServer', inputs.SMTPServer);
  124. }
  125. if (originInputs['SMTPAccount'] !== inputs.SMTPAccount) {
  126. await updateOption('SMTPAccount', inputs.SMTPAccount);
  127. }
  128. if (originInputs['SMTPFrom'] !== inputs.SMTPFrom) {
  129. await updateOption('SMTPFrom', inputs.SMTPFrom);
  130. }
  131. if (
  132. originInputs['SMTPPort'] !== inputs.SMTPPort &&
  133. inputs.SMTPPort !== ''
  134. ) {
  135. await updateOption('SMTPPort', inputs.SMTPPort);
  136. }
  137. if (
  138. originInputs['SMTPToken'] !== inputs.SMTPToken &&
  139. inputs.SMTPToken !== ''
  140. ) {
  141. await updateOption('SMTPToken', inputs.SMTPToken);
  142. }
  143. };
  144. const submitWeChat = async () => {
  145. if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) {
  146. await updateOption(
  147. 'WeChatServerAddress',
  148. removeTrailingSlash(inputs.WeChatServerAddress)
  149. );
  150. }
  151. if (
  152. originInputs['WeChatAccountQRCodeImageURL'] !==
  153. inputs.WeChatAccountQRCodeImageURL
  154. ) {
  155. await updateOption(
  156. 'WeChatAccountQRCodeImageURL',
  157. inputs.WeChatAccountQRCodeImageURL
  158. );
  159. }
  160. if (
  161. originInputs['WeChatServerToken'] !== inputs.WeChatServerToken &&
  162. inputs.WeChatServerToken !== ''
  163. ) {
  164. await updateOption('WeChatServerToken', inputs.WeChatServerToken);
  165. }
  166. };
  167. const submitGitHubOAuth = async () => {
  168. if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) {
  169. await updateOption('GitHubClientId', inputs.GitHubClientId);
  170. }
  171. if (
  172. originInputs['GitHubClientSecret'] !== inputs.GitHubClientSecret &&
  173. inputs.GitHubClientSecret !== ''
  174. ) {
  175. await updateOption('GitHubClientSecret', inputs.GitHubClientSecret);
  176. }
  177. };
  178. const submitTurnstile = async () => {
  179. if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) {
  180. await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey);
  181. }
  182. if (
  183. originInputs['TurnstileSecretKey'] !== inputs.TurnstileSecretKey &&
  184. inputs.TurnstileSecretKey !== ''
  185. ) {
  186. await updateOption('TurnstileSecretKey', inputs.TurnstileSecretKey);
  187. }
  188. };
  189. return (
  190. <Grid columns={1}>
  191. <Grid.Column>
  192. <Form loading={loading}>
  193. <Header as='h3'>通用设置</Header>
  194. <Form.Group widths='equal'>
  195. <Form.Input
  196. label='服务器地址'
  197. placeholder='例如:https://yourdomain.com'
  198. value={inputs.ServerAddress}
  199. name='ServerAddress'
  200. onChange={handleInputChange}
  201. />
  202. </Form.Group>
  203. <Form.Button onClick={submitServerAddress}>
  204. 更新服务器地址
  205. </Form.Button>
  206. <Divider />
  207. <Header as='h3'>配置登录注册</Header>
  208. <Form.Group inline>
  209. <Form.Checkbox
  210. checked={inputs.PasswordLoginEnabled === 'true'}
  211. label='允许通过密码进行登录'
  212. name='PasswordLoginEnabled'
  213. onChange={handleInputChange}
  214. />
  215. <Form.Checkbox
  216. checked={inputs.PasswordRegisterEnabled === 'true'}
  217. label='允许通过密码进行注册'
  218. name='PasswordRegisterEnabled'
  219. onChange={handleInputChange}
  220. />
  221. <Form.Checkbox
  222. checked={inputs.EmailVerificationEnabled === 'true'}
  223. label='通过密码注册时需要进行邮箱验证'
  224. name='EmailVerificationEnabled'
  225. onChange={handleInputChange}
  226. />
  227. <Form.Checkbox
  228. checked={inputs.GitHubOAuthEnabled === 'true'}
  229. label='允许通过 GitHub 账户登录 & 注册'
  230. name='GitHubOAuthEnabled'
  231. onChange={handleInputChange}
  232. />
  233. <Form.Checkbox
  234. checked={inputs.WeChatAuthEnabled === 'true'}
  235. label='允许通过微信登录 & 注册'
  236. name='WeChatAuthEnabled'
  237. onChange={handleInputChange}
  238. />
  239. </Form.Group>
  240. <Form.Group inline>
  241. <Form.Checkbox
  242. checked={inputs.RegisterEnabled === 'true'}
  243. label='允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)'
  244. name='RegisterEnabled'
  245. onChange={handleInputChange}
  246. />
  247. <Form.Checkbox
  248. checked={inputs.TurnstileCheckEnabled === 'true'}
  249. label='启用 Turnstile 用户校验'
  250. name='TurnstileCheckEnabled'
  251. onChange={handleInputChange}
  252. />
  253. </Form.Group>
  254. <Divider />
  255. <Header as='h3'>
  256. 运营设置
  257. </Header>
  258. <Form.Group widths={3}>
  259. <Form.Input
  260. label='新用户初始配额'
  261. name='QuotaForNewUser'
  262. onChange={handleInputChange}
  263. autoComplete='new-password'
  264. value={inputs.QuotaForNewUser}
  265. type='number'
  266. min='0'
  267. placeholder='例如:100'
  268. />
  269. <Form.Input
  270. label='充值链接'
  271. name='TopUpLink'
  272. onChange={handleInputChange}
  273. autoComplete='new-password'
  274. value={inputs.TopUpLink}
  275. type='link'
  276. placeholder='例如发卡网站的购买链接'
  277. />
  278. </Form.Group>
  279. <Form.Group widths='equal'>
  280. <Form.TextArea
  281. label='模型倍率'
  282. name='ModelRatio'
  283. onChange={handleInputChange}
  284. style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
  285. autoComplete='new-password'
  286. value={inputs.ModelRatio}
  287. placeholder='为一个 JSON 文本,键为模型名称,值为倍率'
  288. />
  289. </Form.Group>
  290. <Form.Button onClick={submitOperationConfig}>保存运营设置</Form.Button>
  291. <Divider />
  292. <Header as='h3'>
  293. 监控设置
  294. </Header>
  295. <Form.Group widths={3}>
  296. <Form.Input
  297. label='最长回应时间'
  298. name='ChannelDisableThreshold'
  299. onChange={handleInputChange}
  300. autoComplete='new-password'
  301. value={inputs.ChannelDisableThreshold}
  302. type='number'
  303. min='0'
  304. placeholder='单位秒,当运行通道全部测试时,超过此时间将自动禁用通道'
  305. />
  306. </Form.Group>
  307. <Form.Group inline>
  308. <Form.Checkbox
  309. checked={inputs.AutomaticDisableChannelEnabled === 'true'}
  310. label='失败时自动禁用通道'
  311. name='AutomaticDisableChannelEnabled'
  312. onChange={handleInputChange}
  313. />
  314. </Form.Group>
  315. <Divider />
  316. <Header as='h3'>
  317. 配置 SMTP
  318. <Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
  319. </Header>
  320. <Form.Group widths={3}>
  321. <Form.Input
  322. label='SMTP 服务器地址'
  323. name='SMTPServer'
  324. onChange={handleInputChange}
  325. autoComplete='new-password'
  326. value={inputs.SMTPServer}
  327. placeholder='例如:smtp.qq.com'
  328. />
  329. <Form.Input
  330. label='SMTP 端口'
  331. name='SMTPPort'
  332. onChange={handleInputChange}
  333. autoComplete='new-password'
  334. value={inputs.SMTPPort}
  335. placeholder='默认: 587'
  336. />
  337. <Form.Input
  338. label='SMTP 账户'
  339. name='SMTPAccount'
  340. onChange={handleInputChange}
  341. autoComplete='new-password'
  342. value={inputs.SMTPAccount}
  343. placeholder='通常是邮箱地址'
  344. />
  345. </Form.Group>
  346. <Form.Group widths={3}>
  347. <Form.Input
  348. label='SMTP 发送者邮箱'
  349. name='SMTPFrom'
  350. onChange={handleInputChange}
  351. autoComplete='new-password'
  352. value={inputs.SMTPFrom}
  353. placeholder='通常和邮箱地址保持一致'
  354. />
  355. <Form.Input
  356. label='SMTP 访问凭证'
  357. name='SMTPToken'
  358. onChange={handleInputChange}
  359. type='password'
  360. autoComplete='new-password'
  361. value={inputs.SMTPToken}
  362. placeholder='敏感信息不会发送到前端显示'
  363. />
  364. </Form.Group>
  365. <Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
  366. <Divider />
  367. <Header as='h3'>
  368. 配置 GitHub OAuth App
  369. <Header.Subheader>
  370. 用以支持通过 GitHub 进行登录注册,
  371. <a href='https://github.com/settings/developers' target='_blank'>
  372. 点击此处
  373. </a>
  374. 管理你的 GitHub OAuth App
  375. </Header.Subheader>
  376. </Header>
  377. <Message>
  378. Homepage URL 填 <code>{inputs.ServerAddress}</code>
  379. ,Authorization callback URL 填{' '}
  380. <code>{`${inputs.ServerAddress}/oauth/github`}</code>
  381. </Message>
  382. <Form.Group widths={3}>
  383. <Form.Input
  384. label='GitHub Client ID'
  385. name='GitHubClientId'
  386. onChange={handleInputChange}
  387. autoComplete='new-password'
  388. value={inputs.GitHubClientId}
  389. placeholder='输入你注册的 GitHub OAuth APP 的 ID'
  390. />
  391. <Form.Input
  392. label='GitHub Client Secret'
  393. name='GitHubClientSecret'
  394. onChange={handleInputChange}
  395. type='password'
  396. autoComplete='new-password'
  397. value={inputs.GitHubClientSecret}
  398. placeholder='敏感信息不会发送到前端显示'
  399. />
  400. </Form.Group>
  401. <Form.Button onClick={submitGitHubOAuth}>
  402. 保存 GitHub OAuth 设置
  403. </Form.Button>
  404. <Divider />
  405. <Header as='h3'>
  406. 配置 WeChat Server
  407. <Header.Subheader>
  408. 用以支持通过微信进行登录注册,
  409. <a
  410. href='https://github.com/songquanpeng/wechat-server'
  411. target='_blank'
  412. >
  413. 点击此处
  414. </a>
  415. 了解 WeChat Server
  416. </Header.Subheader>
  417. </Header>
  418. <Form.Group widths={3}>
  419. <Form.Input
  420. label='WeChat Server 服务器地址'
  421. name='WeChatServerAddress'
  422. placeholder='例如:https://yourdomain.com'
  423. onChange={handleInputChange}
  424. autoComplete='new-password'
  425. value={inputs.WeChatServerAddress}
  426. />
  427. <Form.Input
  428. label='WeChat Server 访问凭证'
  429. name='WeChatServerToken'
  430. type='password'
  431. onChange={handleInputChange}
  432. autoComplete='new-password'
  433. value={inputs.WeChatServerToken}
  434. placeholder='敏感信息不会发送到前端显示'
  435. />
  436. <Form.Input
  437. label='微信公众号二维码图片链接'
  438. name='WeChatAccountQRCodeImageURL'
  439. onChange={handleInputChange}
  440. autoComplete='new-password'
  441. value={inputs.WeChatAccountQRCodeImageURL}
  442. placeholder='输入一个图片链接'
  443. />
  444. </Form.Group>
  445. <Form.Button onClick={submitWeChat}>
  446. 保存 WeChat Server 设置
  447. </Form.Button>
  448. <Divider />
  449. <Header as='h3'>
  450. 配置 Turnstile
  451. <Header.Subheader>
  452. 用以支持用户校验,
  453. <a href='https://dash.cloudflare.com/' target='_blank'>
  454. 点击此处
  455. </a>
  456. 管理你的 Turnstile Sites,推荐选择 Invisible Widget Type
  457. </Header.Subheader>
  458. </Header>
  459. <Form.Group widths={3}>
  460. <Form.Input
  461. label='Turnstile Site Key'
  462. name='TurnstileSiteKey'
  463. onChange={handleInputChange}
  464. autoComplete='new-password'
  465. value={inputs.TurnstileSiteKey}
  466. placeholder='输入你注册的 Turnstile Site Key'
  467. />
  468. <Form.Input
  469. label='Turnstile Secret Key'
  470. name='TurnstileSecretKey'
  471. onChange={handleInputChange}
  472. type='password'
  473. autoComplete='new-password'
  474. value={inputs.TurnstileSecretKey}
  475. placeholder='敏感信息不会发送到前端显示'
  476. />
  477. </Form.Group>
  478. <Form.Button onClick={submitTurnstile}>
  479. 保存 Turnstile 设置
  480. </Form.Button>
  481. </Form>
  482. </Grid.Column>
  483. </Grid>
  484. );
  485. };
  486. export default SystemSetting;