New API {process.env.REACT_APP_VERSION}{' '}
由{' '}
-
+
Calcium-Ion
{' '}
开发,基于{' '}
-
+
One API v0.5.4
{' '}
,本项目根据{' '}
-
+
MIT 许可证
{' '}
授权
diff --git a/web/src/components/GitHubOAuth.js b/web/src/components/GitHubOAuth.js
index 965d93c..50c6306 100644
--- a/web/src/components/GitHubOAuth.js
+++ b/web/src/components/GitHubOAuth.js
@@ -58,7 +58,7 @@ const GitHubOAuth = () => {
return (
- {prompt}
+ {prompt}
);
diff --git a/web/src/components/HeaderBar.js b/web/src/components/HeaderBar.js
index 244cbd8..eaf36c4 100644
--- a/web/src/components/HeaderBar.js
+++ b/web/src/components/HeaderBar.js
@@ -1,165 +1,161 @@
-import React, {useContext, useEffect, useRef, useState} from 'react';
-import {Link, useNavigate} from 'react-router-dom';
-import {UserContext} from '../context/User';
+import React, { useContext, useEffect, useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { UserContext } from '../context/User';
-import {API, getLogo, getSystemName, isAdmin, isMobile, showSuccess} from '../helpers';
+import { API, getLogo, getSystemName, showSuccess } from '../helpers';
import '../index.css';
import fireworks from 'react-fireworks';
-import {
- IconKey,
- IconUser,
- IconHelpCircle
-} from '@douyinfe/semi-icons';
-import {Nav, Avatar, Dropdown, Layout, Switch} from '@douyinfe/semi-ui';
-import {stringToColor} from "../helpers/render";
+import { IconHelpCircle, IconKey, IconUser } from '@douyinfe/semi-icons';
+import { Avatar, Dropdown, Layout, Nav, Switch } from '@douyinfe/semi-ui';
+import { stringToColor } from '../helpers/render';
// HeaderBar Buttons
let headerButtons = [
- {
- text: '关于',
- itemKey: 'about',
- to: '/about',
- icon:
- },
+ {
+ text: '关于',
+ itemKey: 'about',
+ to: '/about',
+ icon:
+ }
];
if (localStorage.getItem('chat_link')) {
- headerButtons.splice(1, 0, {
- name: '聊天',
- to: '/chat',
- icon: 'comments'
- });
+ headerButtons.splice(1, 0, {
+ name: '聊天',
+ to: '/chat',
+ icon: 'comments'
+ });
}
const HeaderBar = () => {
- const [userState, userDispatch] = useContext(UserContext);
- let navigate = useNavigate();
+ const [userState, userDispatch] = useContext(UserContext);
+ let navigate = useNavigate();
- const [showSidebar, setShowSidebar] = useState(false);
- const [dark, setDark] = useState(false);
- const systemName = getSystemName();
- const logo = getLogo();
- var themeMode = localStorage.getItem('theme-mode');
- const currentDate = new Date();
- // enable fireworks on new year(1.1 and 2.9-2.24)
- const isNewYear = (currentDate.getMonth() === 0 && currentDate.getDate() === 1) || (currentDate.getMonth() === 1 && currentDate.getDate() >= 9 && currentDate.getDate() <= 24);
+ const [showSidebar, setShowSidebar] = useState(false);
+ const [dark, setDark] = useState(false);
+ const systemName = getSystemName();
+ const logo = getLogo();
+ var themeMode = localStorage.getItem('theme-mode');
+ const currentDate = new Date();
+ // enable fireworks on new year(1.1 and 2.9-2.24)
+ const isNewYear = (currentDate.getMonth() === 0 && currentDate.getDate() === 1) || (currentDate.getMonth() === 1 && currentDate.getDate() >= 9 && currentDate.getDate() <= 24);
- async function logout() {
- setShowSidebar(false);
- await API.get('/api/user/logout');
- showSuccess('注销成功!');
- userDispatch({type: 'logout'});
- localStorage.removeItem('user');
- navigate('/login');
+ async function logout() {
+ setShowSidebar(false);
+ await API.get('/api/user/logout');
+ showSuccess('注销成功!');
+ userDispatch({ type: 'logout' });
+ localStorage.removeItem('user');
+ navigate('/login');
+ }
+
+ const handleNewYearClick = () => {
+ fireworks.init('root', {});
+ fireworks.start();
+ setTimeout(() => {
+ fireworks.stop();
+ setTimeout(() => {
+ window.location.reload();
+ }, 10000);
+ }, 3000);
+ };
+
+ useEffect(() => {
+ if (themeMode === 'dark') {
+ switchMode(true);
}
+ if (isNewYear) {
+ console.log('Happy New Year!');
+ }
+ }, []);
- const handleNewYearClick = () => {
- fireworks.init("root",{});
- fireworks.start();
- setTimeout(() => {
- fireworks.stop();
- setTimeout(() => {
- window.location.reload();
- }, 10000);
- }, 3000);
- };
+ const switchMode = (model) => {
+ const body = document.body;
+ if (!model) {
+ body.removeAttribute('theme-mode');
+ localStorage.setItem('theme-mode', 'light');
+ } else {
+ body.setAttribute('theme-mode', 'dark');
+ localStorage.setItem('theme-mode', 'dark');
+ }
+ setDark(model);
+ };
+ return (
+ <>
+
+
+
+
+
+ >
+ );
};
export default HeaderBar;
diff --git a/web/src/components/Loading.js b/web/src/components/Loading.js
index 1210a56..bacb53b 100644
--- a/web/src/components/Loading.js
+++ b/web/src/components/Loading.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { Segment, Dimmer, Loader } from 'semantic-ui-react';
+import { Dimmer, Loader, Segment } from 'semantic-ui-react';
const Loading = ({ prompt: name = 'page' }) => {
return (
diff --git a/web/src/components/LoginForm.js b/web/src/components/LoginForm.js
index baef358..9657085 100644
--- a/web/src/components/LoginForm.js
+++ b/web/src/components/LoginForm.js
@@ -1,12 +1,12 @@
import React, { useContext, useEffect, useState } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { UserContext } from '../context/User';
-import { API, getLogo, isMobile, showError, showInfo, showSuccess, showWarning } from '../helpers';
+import { API, getLogo, showError, showInfo, showSuccess } from '../helpers';
import { onGitHubOAuthClicked, onLinuxDoOAuthClicked } from './utils';
-import Turnstile from "react-turnstile";
-import { Layout, Card, Image, Form, Button, Divider, Modal, Icon } from '@douyinfe/semi-ui';
-import Title from "@douyinfe/semi-ui/lib/es/typography/title";
-import Text from "@douyinfe/semi-ui/lib/es/typography/text";
+import Turnstile from 'react-turnstile';
+import { Button, Card, Divider, Form, Icon, Layout, Modal } from '@douyinfe/semi-ui';
+import Title from '@douyinfe/semi-ui/lib/es/typography/title';
+import Text from '@douyinfe/semi-ui/lib/es/typography/text';
import TelegramLoginButton from 'react-telegram-login';
import { IconGithubLogo } from '@douyinfe/semi-icons';
@@ -14,252 +14,252 @@ import LinuxDoIcon from './LinuxDoIcon';
import WeChatIcon from './WeChatIcon';
const LoginForm = () => {
- const [inputs, setInputs] = useState({
- username: '',
- password: '',
- wechat_verification_code: ''
- });
- const [searchParams, setSearchParams] = useSearchParams();
- const [submitted, setSubmitted] = useState(false);
- const { username, password } = inputs;
- const [userState, userDispatch] = useContext(UserContext);
- const [turnstileEnabled, setTurnstileEnabled] = useState(false);
- const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
- const [turnstileToken, setTurnstileToken] = useState('');
- let navigate = useNavigate();
- const [status, setStatus] = useState({});
- const logo = getLogo();
+ const [inputs, setInputs] = useState({
+ username: '',
+ password: '',
+ wechat_verification_code: ''
+ });
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [submitted, setSubmitted] = useState(false);
+ const { username, password } = inputs;
+ const [userState, userDispatch] = useContext(UserContext);
+ const [turnstileEnabled, setTurnstileEnabled] = useState(false);
+ const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
+ const [turnstileToken, setTurnstileToken] = useState('');
+ let navigate = useNavigate();
+ const [status, setStatus] = useState({});
+ const logo = getLogo();
- useEffect(() => {
- if (searchParams.get('expired')) {
- showError('未登录或登录已过期,请重新登录!');
- }
- let status = localStorage.getItem('status');
- if (status) {
- status = JSON.parse(status);
- setStatus(status);
- if (status.turnstile_check) {
- setTurnstileEnabled(true);
- setTurnstileSiteKey(status.turnstile_site_key);
- }
- }
- }, []);
-
- const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
-
- const onWeChatLoginClicked = () => {
- setShowWeChatLoginModal(true);
- };
-
- const onSubmitWeChatVerificationCode = async () => {
- if (turnstileEnabled && turnstileToken === '') {
- showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
- return;
- }
- const res = await API.get(
- `/api/oauth/wechat?code=${inputs.wechat_verification_code}`
- );
- const { success, message, data } = res.data;
- if (success) {
- userDispatch({ type: 'login', payload: data });
- localStorage.setItem('user', JSON.stringify(data));
- navigate('/');
- showSuccess('登录成功!');
- setShowWeChatLoginModal(false);
- } else {
- showError(message);
- }
- };
-
- function handleChange(name, value) {
- setInputs((inputs) => ({ ...inputs, [name]: value }));
+ useEffect(() => {
+ if (searchParams.get('expired')) {
+ showError('未登录或登录已过期,请重新登录!');
}
-
- async function handleSubmit(e) {
- if (turnstileEnabled && turnstileToken === '') {
- showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
- return;
- }
- setSubmitted(true);
- if (username && password) {
- const res = await API.post(`/api/user/login?turnstile=${turnstileToken}`, {
- username,
- password
- });
- const { success, message, data } = res.data;
- if (success) {
- userDispatch({ type: 'login', payload: data });
- localStorage.setItem('user', JSON.stringify(data));
- showSuccess('登录成功!');
- if (username === 'root' && password === '123456') {
- Modal.error({ title: '您正在使用默认密码!', content: '请立刻修改默认密码!', centered: true });
- }
- navigate('/token');
- } else {
- showError(message);
- }
- } else {
- showError('请输入用户名和密码!');
- }
+ let status = localStorage.getItem('status');
+ if (status) {
+ status = JSON.parse(status);
+ setStatus(status);
+ if (status.turnstile_check) {
+ setTurnstileEnabled(true);
+ setTurnstileSiteKey(status.turnstile_site_key);
+ }
}
+ }, []);
- // 添加Telegram登录处理函数
- const onTelegramLoginClicked = async (response) => {
- const fields = ["id", "first_name", "last_name", "username", "photo_url", "auth_date", "hash", "lang"];
- const params = {};
- fields.forEach((field) => {
- if (response[field]) {
- params[field] = response[field];
- }
- });
- const res = await API.get(`/api/oauth/telegram/login`, { params });
- const { success, message, data } = res.data;
- if (success) {
- userDispatch({ type: 'login', payload: data });
- localStorage.setItem('user', JSON.stringify(data));
- showSuccess('登录成功!');
- navigate('/');
- } else {
- showError(message);
- }
- };
+ const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
- return (
-
-
-
-
-
-
-
-
-
- 用户登录
-
- handleChange('username', value)}
- />
- handleChange('password', value)}
- />
+ const onWeChatLoginClicked = () => {
+ setShowWeChatLoginModal(true);
+ };
-
-
-
-
- 没有账号请先 注册账号
-
-
- 忘记密码 点击重置
-
-
- {status.github_oauth || status.linuxdo_oauth || status.wechat_login || status.telegram_oauth ? (
- <>
-
- 第三方登录
-
-
- {status.github_oauth ? (
- }
- onClick={() => onGitHubOAuthClicked(status.github_client_id)}
- />
- ) : (
- <>>
- )}
- {status.linuxdo_oauth ? (
- }
- style={{color: '#000'}}
- onClick={() => onLinuxDoOAuthClicked(status.linuxdo_client_id)}
- />
- ) : (
- <>>
- )}
- {status.wechat_login ? (
- } />}
- onClick={onWeChatLoginClicked}
- />
- ) : (
- <>>
- )}
-
- {status.telegram_oauth ? (
-
- ) : (
- <>>
- )}
-
- >
- ) : (
- <>>
- )}
- setShowWeChatLoginModal(false)}
- okText={'登录'}
- size={'small'}
- centered={true}
- >
-
-

-
-
-
- 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
-
-
- handleChange('wechat_verification_code', value)}
- />
-
-
-
- {turnstileEnabled ? (
-
- {
- setTurnstileToken(token);
- }}
- />
-
- ) : (
- <>>
- )}
-
-
-
-
-
-
+ const onSubmitWeChatVerificationCode = async () => {
+ if (turnstileEnabled && turnstileToken === '') {
+ showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
+ return;
+ }
+ const res = await API.get(
+ `/api/oauth/wechat?code=${inputs.wechat_verification_code}`
);
+ const { success, message, data } = res.data;
+ if (success) {
+ userDispatch({ type: 'login', payload: data });
+ localStorage.setItem('user', JSON.stringify(data));
+ navigate('/');
+ showSuccess('登录成功!');
+ setShowWeChatLoginModal(false);
+ } else {
+ showError(message);
+ }
+ };
+
+ function handleChange(name, value) {
+ setInputs((inputs) => ({ ...inputs, [name]: value }));
+ }
+
+ async function handleSubmit(e) {
+ if (turnstileEnabled && turnstileToken === '') {
+ showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
+ return;
+ }
+ setSubmitted(true);
+ if (username && password) {
+ const res = await API.post(`/api/user/login?turnstile=${turnstileToken}`, {
+ username,
+ password
+ });
+ const { success, message, data } = res.data;
+ if (success) {
+ userDispatch({ type: 'login', payload: data });
+ localStorage.setItem('user', JSON.stringify(data));
+ showSuccess('登录成功!');
+ if (username === 'root' && password === '123456') {
+ Modal.error({ title: '您正在使用默认密码!', content: '请立刻修改默认密码!', centered: true });
+ }
+ navigate('/token');
+ } else {
+ showError(message);
+ }
+ } else {
+ showError('请输入用户名和密码!');
+ }
+ }
+
+ // 添加Telegram登录处理函数
+ const onTelegramLoginClicked = async (response) => {
+ const fields = ['id', 'first_name', 'last_name', 'username', 'photo_url', 'auth_date', 'hash', 'lang'];
+ const params = {};
+ fields.forEach((field) => {
+ if (response[field]) {
+ params[field] = response[field];
+ }
+ });
+ const res = await API.get(`/api/oauth/telegram/login`, { params });
+ const { success, message, data } = res.data;
+ if (success) {
+ userDispatch({ type: 'login', payload: data });
+ localStorage.setItem('user', JSON.stringify(data));
+ showSuccess('登录成功!');
+ navigate('/');
+ } else {
+ showError(message);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ 用户登录
+
+ handleChange('username', value)}
+ />
+ handleChange('password', value)}
+ />
+
+
+
+
+
+ 没有账号请先 注册账号
+
+
+ 忘记密码 点击重置
+
+
+ {status.github_oauth || status.linuxdo_oauth || status.wechat_login || status.telegram_oauth ? (
+ <>
+
+ 第三方登录
+
+
+ {status.github_oauth ? (
+ }
+ onClick={() => onGitHubOAuthClicked(status.github_client_id)}
+ />
+ ) : (
+ <>>
+ )}
+ {status.linuxdo_oauth ? (
+ }
+ style={{color: '#000'}}
+ onClick={() => onLinuxDoOAuthClicked(status.linuxdo_client_id)}
+ />
+ ) : (
+ <>>
+ )}
+ {status.wechat_login ? (
+ } />}
+ onClick={onWeChatLoginClicked}
+ />
+ ) : (
+ <>>
+ )}
+
+ {status.telegram_oauth ? (
+
+ ) : (
+ <>>
+ )}
+
+ >
+ ) : (
+ <>>
+ )}
+ setShowWeChatLoginModal(false)}
+ okText={'登录'}
+ size={'small'}
+ centered={true}
+ >
+
+

+
+
+
+ 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
+
+
+ handleChange('wechat_verification_code', value)}
+ />
+
+
+
+ {turnstileEnabled ? (
+
+ {
+ setTurnstileToken(token);
+ }}
+ />
+
+ ) : (
+ <>>
+ )}
+
+
+
+
+
+
+ );
};
export default LoginForm;
diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js
index 9fb7e3c..b07682b 100644
--- a/web/src/components/LogsTable.js
+++ b/web/src/components/LogsTable.js
@@ -1,501 +1,399 @@
-import React, {useEffect, useState} from 'react';
-import {Label} from 'semantic-ui-react';
-import {API, copy, isAdmin, showError, showSuccess, timestamp2string} from '../helpers';
+import React, { useEffect, useState } from 'react';
+import { API, copy, isAdmin, showError, showSuccess, timestamp2string } from '../helpers';
-import {Table, Avatar, Tag, Form, Button, Layout, Select, Popover, Modal, Spin, Space} from '@douyinfe/semi-ui';
-import {ITEMS_PER_PAGE} from '../constants';
-import {renderNumber, renderQuota, stringToColor} from '../helpers/render';
-import {
- IconAt,
- IconHistogram,
- IconGift,
- IconKey,
- IconUser,
- IconLayers,
- IconSetting,
- IconCreditCard,
- IconSemiLogo,
- IconHome,
- IconMore
-} from '@douyinfe/semi-icons';
-import Paragraph from "@douyinfe/semi-ui/lib/es/typography/paragraph";
+import { Avatar, Button, Form, Layout, Modal, Select, Space, Spin, Table, Tag } from '@douyinfe/semi-ui';
+import { ITEMS_PER_PAGE } from '../constants';
+import { renderNumber, renderQuota, stringToColor } from '../helpers/render';
+import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
+
+const { Header } = Layout;
-const {Header} = Layout;
function renderTimestamp(timestamp) {
- return (
- <>
- {timestamp2string(timestamp)}
- >
- );
+ return (<>
+ {timestamp2string(timestamp)}
+ >);
}
-const MODE_OPTIONS = [
- {key: 'all', text: '全部用户', value: 'all'},
- {key: 'self', text: '当前用户', value: 'self'}
-];
+const MODE_OPTIONS = [{ key: 'all', text: '全部用户', value: 'all' }, { key: 'self', text: '当前用户', value: 'self' }];
-const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo',
- 'light-blue', 'lime', 'orange', 'pink',
- 'purple', 'red', 'teal', 'violet', 'yellow'
-]
+const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo', 'light-blue', 'lime', 'orange', 'pink', 'purple', 'red', 'teal', 'violet', 'yellow'];
function renderType(type) {
- switch (type) {
- case 1:
- return
充值 ;
- case 2:
- return
消费 ;
- case 3:
- return
管理 ;
- case 4:
- return
系统 ;
- default:
- return
未知 ;
- }
+ switch (type) {
+ case 1:
+ return
充值 ;
+ case 2:
+ return
消费 ;
+ case 3:
+ return
管理 ;
+ case 4:
+ return
系统 ;
+ default:
+ return
未知 ;
+ }
}
function renderIsStream(bool) {
- if (bool) {
- return
流;
- } else {
- return
非流;
- }
+ if (bool) {
+ return
流;
+ } else {
+ return
非流;
+ }
}
function renderUseTime(type) {
- const time = parseInt(type);
- if (time < 101) {
- return
{time} s ;
- } else if (time < 300) {
- return
{time} s ;
- } else {
- return
{time} s ;
- }
+ const time = parseInt(type);
+ if (time < 101) {
+ return
{time} s ;
+ } else if (time < 300) {
+ return
{time} s ;
+ } else {
+ return
{time} s ;
+ }
}
const LogsTable = () => {
- const columns = [
- {
- title: '时间',
- dataIndex: 'timestamp2string',
- },
- {
- title: '渠道',
- dataIndex: 'channel',
- className: isAdmin() ? 'tableShow' : 'tableHiddle',
- render: (text, record, index) => {
- return (
- isAdminUser ?
- record.type === 0 || record.type === 2 ?
-
- { {text} }
-
- :
- <>>
- :
- <>>
- );
- },
- },
- {
- title: '用户',
- dataIndex: 'username',
- className: isAdmin() ? 'tableShow' : 'tableHiddle',
- render: (text, record, index) => {
- return (
- isAdminUser ?
-
-
showUserInfo(record.user_id)}>
- {typeof text === 'string' && text.slice(0, 1)}
-
- {text}
-
- :
- <>>
- );
- },
- },
- {
- title: '令牌',
- dataIndex: 'token_name',
- render: (text, record, index) => {
- return (
- record.type === 0 || record.type === 2 ?
-
- {
- copyText(text)
- }}> {text}
-
- :
- <>>
- );
- },
- },
- {
- title: '类型',
- dataIndex: 'type',
- render: (text, record, index) => {
- return (
-
- {renderType(text)}
-
- );
- },
- },
- {
- title: '模型',
- dataIndex: 'model_name',
- render: (text, record, index) => {
- return (
- record.type === 0 || record.type === 2 ?
-
- {
- copyText(text)
- }}> {text}
-
- :
- <>>
- );
- },
- },
- {
- title: '用时',
- dataIndex: 'use_time',
- render: (text, record, index) => {
- return (
-
-
- {renderUseTime(text)}
- {renderIsStream(record.is_stream)}
-
-
- );
- },
- },
- {
- title: '提示',
- dataIndex: 'prompt_tokens',
- render: (text, record, index) => {
- return (
- record.type === 0 || record.type === 2 ?
-
- { {text} }
-
- :
- <>>
- );
- },
- },
- {
- title: '补全',
- dataIndex: 'completion_tokens',
- render: (text, record, index) => {
- return (
- parseInt(text) > 0 && (record.type === 0 || record.type === 2) ?
-
- { {text} }
-
- :
- <>>
- );
- },
- },
- {
- title: '花费',
- dataIndex: 'quota',
- render: (text, record, index) => {
- return (
- record.type === 0 || record.type === 2 ?
-
- {
- renderQuota(text, 6)
- }
-
- :
- <>>
- );
- }
- },
- {
- title: '详情',
- dataIndex: 'content',
- render: (text, record, index) => {
- return
- {text}
-
- }
- }
- ];
-
- const [logs, setLogs] = useState([]);
- const [showStat, setShowStat] = useState(false);
- const [loading, setLoading] = useState(false);
- const [loadingStat, setLoadingStat] = useState(false);
- const [activePage, setActivePage] = useState(1);
- const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
- const [searchKeyword, setSearchKeyword] = useState('');
- const [searching, setSearching] = useState(false);
- const [logType, setLogType] = useState(0);
- const isAdminUser = isAdmin();
- let now = new Date();
- // 初始化start_timestamp为前一天
- const [inputs, setInputs] = useState({
- username: '',
- token_name: '',
- model_name: '',
- start_timestamp: timestamp2string(now.getTime() / 1000 - 86400),
- end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
- channel: ''
- });
- const {username, token_name, model_name, start_timestamp, end_timestamp, channel} = inputs;
-
- const [stat, setStat] = useState({
- quota: 0,
- token: 0
- });
-
- const handleInputChange = (value, name) => {
- setInputs((inputs) => ({...inputs, [name]: value}));
- };
-
- const getLogSelfStat = async () => {
- let localStartTimestamp = Date.parse(start_timestamp) / 1000;
- let localEndTimestamp = Date.parse(end_timestamp) / 1000;
- let res = await API.get(`/api/log/self/stat?type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`);
- const {success, message, data} = res.data;
- if (success) {
- setStat(data);
- } else {
- showError(message);
- }
- };
-
- const getLogStat = async () => {
- let localStartTimestamp = Date.parse(start_timestamp) / 1000;
- let localEndTimestamp = Date.parse(end_timestamp) / 1000;
- let res = await API.get(`/api/log/stat?type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}`);
- const {success, message, data} = res.data;
- if (success) {
- setStat(data);
- } else {
- showError(message);
- }
- };
-
- const handleEyeClick = async () => {
- setLoadingStat(true);
- if (isAdminUser) {
- await getLogStat();
- } else {
- await getLogSelfStat();
- }
- setShowStat(true);
- setLoadingStat(false);
- };
-
- const showUserInfo = async (userId) => {
- if (!isAdminUser) {
- return;
- }
- const res = await API.get(`/api/user/${userId}`);
- const {success, message, data} = res.data;
- if (success) {
- Modal.info({
- title: '用户信息',
- content:
-
用户名: {data.username}
-
余额: {renderQuota(data.quota)}
-
已用额度:{renderQuota(data.used_quota)}
-
请求次数:{renderNumber(data.request_count)}
-
,
- centered: true,
- })
- } else {
- showError(message);
- }
- };
-
- const setLogsFormat = (logs) => {
- for (let i = 0; i < logs.length; i++) {
- logs[i].timestamp2string = timestamp2string(logs[i].created_at);
- logs[i].key = '' + logs[i].id;
- }
- // data.key = '' + data.id
- setLogs(logs);
- setLogCount(logs.length + ITEMS_PER_PAGE);
- // console.log(logCount);
+ const columns = [{
+ title: '时间', dataIndex: 'timestamp2string'
+ }, {
+ title: '渠道',
+ dataIndex: 'channel',
+ className: isAdmin() ? 'tableShow' : 'tableHiddle',
+ render: (text, record, index) => {
+ return (isAdminUser ? record.type === 0 || record.type === 2 ?
+ { {text} }
+
: <>> : <>>);
}
-
- const loadLogs = async (startIdx) => {
- setLoading(true);
-
- let url = '';
- let localStartTimestamp = Date.parse(start_timestamp) / 1000;
- let localEndTimestamp = Date.parse(end_timestamp) / 1000;
- if (isAdminUser) {
- url = `/api/log/?p=${startIdx}&type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}`;
- } else {
- url = `/api/log/self/?p=${startIdx}&type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
- }
- const res = await API.get(url);
- const {success, message, data} = res.data;
- if (success) {
- if (startIdx === 0) {
- setLogsFormat(data);
- } else {
- let newLogs = [...logs];
- newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
- setLogsFormat(newLogs);
- }
- } else {
- showError(message);
- }
- setLoading(false);
- };
-
- const pageData = logs.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE);
-
- const handlePageChange = page => {
- setActivePage(page);
- if (page === Math.ceil(logs.length / ITEMS_PER_PAGE) + 1) {
- // In this case we have to load more data and then append them.
- loadLogs(page - 1).then(r => {
- });
- }
- };
-
- const refresh = async () => {
- // setLoading(true);
- setActivePage(1);
- await loadLogs(0);
- };
-
- const copyText = async (text) => {
- if (await copy(text)) {
- showSuccess('已复制:' + text);
- } else {
- // setSearchKeyword(text);
- Modal.error({title: '无法复制到剪贴板,请手动复制', content: text});
- }
+ }, {
+ title: '用户',
+ dataIndex: 'username',
+ className: isAdmin() ? 'tableShow' : 'tableHiddle',
+ render: (text, record, index) => {
+ return (isAdminUser ?
+
showUserInfo(record.user_id)}>
+ {typeof text === 'string' && text.slice(0, 1)}
+
+ {text}
+
: <>>);
}
+ }, {
+ title: '令牌', dataIndex: 'token_name', render: (text, record, index) => {
+ return (record.type === 0 || record.type === 2 ?
+ {
+ copyText(text);
+ }}> {text}
+
: <>>);
+ }
+ }, {
+ title: '类型', dataIndex: 'type', render: (text, record, index) => {
+ return (
+ {renderType(text)}
+
);
+ }
+ }, {
+ title: '模型', dataIndex: 'model_name', render: (text, record, index) => {
+ return (record.type === 0 || record.type === 2 ?
+ {
+ copyText(text);
+ }}> {text}
+
: <>>);
+ }
+ }, {
+ title: '用时', dataIndex: 'use_time', render: (text, record, index) => {
+ return (
+
+ {renderUseTime(text)}
+ {renderIsStream(record.is_stream)}
+
+
);
+ }
+ }, {
+ title: '提示', dataIndex: 'prompt_tokens', render: (text, record, index) => {
+ return (record.type === 0 || record.type === 2 ?
+ { {text} }
+
: <>>);
+ }
+ }, {
+ title: '补全', dataIndex: 'completion_tokens', render: (text, record, index) => {
+ return (parseInt(text) > 0 && (record.type === 0 || record.type === 2) ?
+ { {text} }
+
: <>>);
+ }
+ }, {
+ title: '花费', dataIndex: 'quota', render: (text, record, index) => {
+ return (record.type === 0 || record.type === 2 ?
+ {renderQuota(text, 6)}
+
: <>>);
+ }
+ }, {
+ title: '详情', dataIndex: 'content', render: (text, record, index) => {
+ return
+ {text}
+ ;
+ }
+ }];
- useEffect(() => {
- refresh().then();
- }, [logType]);
+ const [logs, setLogs] = useState([]);
+ const [showStat, setShowStat] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [loadingStat, setLoadingStat] = useState(false);
+ const [activePage, setActivePage] = useState(1);
+ const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
+ const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
+ const [searchKeyword, setSearchKeyword] = useState('');
+ const [searching, setSearching] = useState(false);
+ const [logType, setLogType] = useState(0);
+ const isAdminUser = isAdmin();
+ let now = new Date();
+ // 初始化start_timestamp为前一天
+ const [inputs, setInputs] = useState({
+ username: '',
+ token_name: '',
+ model_name: '',
+ start_timestamp: timestamp2string(now.getTime() / 1000 - 86400),
+ end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
+ channel: ''
+ });
+ const { username, token_name, model_name, start_timestamp, end_timestamp, channel } = inputs;
- const searchLogs = async () => {
- if (searchKeyword === '') {
- // if keyword is blank, load files instead.
- await loadLogs(0);
- setActivePage(1);
- return;
- }
- setSearching(true);
- const res = await API.get(`/api/log/self/search?keyword=${searchKeyword}`);
- const {success, message, data} = res.data;
- if (success) {
- setLogs(data);
- setActivePage(1);
- } else {
- showError(message);
- }
- setSearching(false);
- };
+ const [stat, setStat] = useState({
+ quota: 0, token: 0
+ });
- const handleKeywordChange = async (e, {value}) => {
- setSearchKeyword(value.trim());
- };
+ const handleInputChange = (value, name) => {
+ setInputs((inputs) => ({ ...inputs, [name]: value }));
+ };
- const sortLog = (key) => {
- if (logs.length === 0) return;
- setLoading(true);
- let sortedLogs = [...logs];
- if (typeof sortedLogs[0][key] === 'string') {
- sortedLogs.sort((a, b) => {
- return ('' + a[key]).localeCompare(b[key]);
- });
- } else {
- sortedLogs.sort((a, b) => {
- if (a[key] === b[key]) return 0;
- if (a[key] > b[key]) return -1;
- if (a[key] < b[key]) return 1;
- });
- }
- if (sortedLogs[0].id === logs[0].id) {
- sortedLogs.reverse();
- }
- setLogs(sortedLogs);
- setLoading(false);
- };
+ const getLogSelfStat = async () => {
+ let localStartTimestamp = Date.parse(start_timestamp) / 1000;
+ let localEndTimestamp = Date.parse(end_timestamp) / 1000;
+ let res = await API.get(`/api/log/self/stat?type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ setStat(data);
+ } else {
+ showError(message);
+ }
+ };
- return (
+ const getLogStat = async () => {
+ let localStartTimestamp = Date.parse(start_timestamp) / 1000;
+ let localEndTimestamp = Date.parse(end_timestamp) / 1000;
+ let res = await API.get(`/api/log/stat?type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ setStat(data);
+ } else {
+ showError(message);
+ }
+ };
+
+ const handleEyeClick = async () => {
+ setLoadingStat(true);
+ if (isAdminUser) {
+ await getLogStat();
+ } else {
+ await getLogSelfStat();
+ }
+ setShowStat(true);
+ setLoadingStat(false);
+ };
+
+ const showUserInfo = async (userId) => {
+ if (!isAdminUser) {
+ return;
+ }
+ const res = await API.get(`/api/user/${userId}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ Modal.info({
+ title: '用户信息', content:
+
用户名: {data.username}
+
余额: {renderQuota(data.quota)}
+
已用额度:{renderQuota(data.used_quota)}
+
请求次数:{renderNumber(data.request_count)}
+
, centered: true
+ });
+ } else {
+ showError(message);
+ }
+ };
+
+ const setLogsFormat = (logs) => {
+ for (let i = 0; i < logs.length; i++) {
+ logs[i].timestamp2string = timestamp2string(logs[i].created_at);
+ logs[i].key = '' + logs[i].id;
+ }
+ // data.key = '' + data.id
+ setLogs(logs);
+ setLogCount(logs.length + ITEMS_PER_PAGE);
+ // console.log(logCount);
+ };
+
+ const loadLogs = async (startIdx, pageSize, logType = 0) => {
+ setLoading(true);
+
+ let url = '';
+ let localStartTimestamp = Date.parse(start_timestamp) / 1000;
+ let localEndTimestamp = Date.parse(end_timestamp) / 1000;
+ if (isAdminUser) {
+ url = `/api/log/?p=${startIdx}&page_size=${pageSize}&type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}`;
+ } else {
+ url = `/api/log/self/?p=${startIdx}&page_size=${pageSize}&type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
+ }
+ const res = await API.get(url);
+ const { success, message, data } = res.data;
+ if (success) {
+ if (startIdx === 0) {
+ setLogsFormat(data);
+ } else {
+ let newLogs = [...logs];
+ newLogs.splice(startIdx * pageSize, data.length, ...data);
+ setLogsFormat(newLogs);
+ }
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
+
+ const pageData = logs.slice((activePage - 1) * pageSize, activePage * pageSize);
+
+ const handlePageChange = page => {
+ setActivePage(page);
+ if (page === Math.ceil(logs.length / pageSize) + 1) {
+ // In this case we have to load more data and then append them.
+ loadLogs(page - 1, pageSize, logType).then(r => {
+ });
+ }
+ };
+
+ const handlePageSizeChange = async (size) => {
+ localStorage.setItem('page-size', size + '');
+ setPageSize(size);
+ setActivePage(1);
+ loadLogs(0, size)
+ .then()
+ .catch((reason) => {
+ showError(reason);
+ });
+ };
+
+ const refresh = async (localLogType) => {
+ // setLoading(true);
+ setActivePage(1);
+ await loadLogs(0, pageSize, localLogType);
+ };
+
+ const copyText = async (text) => {
+ if (await copy(text)) {
+ showSuccess('已复制:' + text);
+ } else {
+ // setSearchKeyword(text);
+ Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
+ }
+ };
+
+ useEffect(() => {
+ // console.log('default effect')
+ const localPageSize = parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE;
+ setPageSize(localPageSize);
+ loadLogs(0, localPageSize)
+ .then()
+ .catch((reason) => {
+ showError(reason);
+ });
+ }, []);
+
+ const searchLogs = async () => {
+ if (searchKeyword === '') {
+ // if keyword is blank, load files instead.
+ await loadLogs(0, pageSize);
+ setActivePage(1);
+ return;
+ }
+ setSearching(true);
+ const res = await API.get(`/api/log/self/search?keyword=${searchKeyword}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ setLogs(data);
+ setActivePage(1);
+ } else {
+ showError(message);
+ }
+ setSearching(false);
+ };
+
+ return (<>
+
+
+
+ 使用明细(总消耗额度:
+ {showStat ? renderQuota(stat.quota) : '点击查看'}
+ )
+
+
+
+
-
-
-
+
handleInputChange(value, 'token_name')} />
+ handleInputChange(value, 'model_name')} />
+ handleInputChange(value, 'start_timestamp')} />
+ handleInputChange(value, 'end_timestamp')} />
+ {isAdminUser && <>
+ handleInputChange(value, 'channel')} />
+ handleInputChange(value, 'username')} />
+ >}
+
+
+
>
- );
+
+ {
+ handlePageSizeChange(size).then();
+ },
+ onPageChange: handlePageChange
+ }} />
+
+
+ >);
};
export default LogsTable;
diff --git a/web/src/components/MjLogsTable.js b/web/src/components/MjLogsTable.js
index 90c55f1..6a6fbd9 100644
--- a/web/src/components/MjLogsTable.js
+++ b/web/src/components/MjLogsTable.js
@@ -1,454 +1,454 @@
-import React, {useEffect, useState} from 'react';
-import {API, copy, isAdmin, showError, showSuccess, timestamp2string} from '../helpers';
+import React, { useEffect, useState } from 'react';
+import { API, copy, isAdmin, showError, showSuccess, timestamp2string } from '../helpers';
-import {
- Table,
- Avatar,
- Tag,
- Form,
- Button,
- Layout,
- Select,
- Popover,
- Modal,
- ImagePreview,
- Typography, Progress
-} from '@douyinfe/semi-ui';
-import {ITEMS_PER_PAGE} from '../constants';
-import {renderNumber, renderQuota, stringToColor} from '../helpers/render';
+import { Banner, Button, Form, ImagePreview, Layout, Modal, Progress, Table, Tag, Typography } from '@douyinfe/semi-ui';
+import { ITEMS_PER_PAGE } from '../constants';
const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo',
- 'light-blue', 'lime', 'orange', 'pink',
- 'purple', 'red', 'teal', 'violet', 'yellow'
-]
+ 'light-blue', 'lime', 'orange', 'pink',
+ 'purple', 'red', 'teal', 'violet', 'yellow'
+];
function renderType(type) {
- switch (type) {
- case 'IMAGINE':
- return 绘图;
- case 'UPSCALE':
- return 放大;
- case 'VARIATION':
- return 变换;
- case 'HIGH_VARIATION':
- return 强变换;
- case 'LOW_VARIATION':
- return 弱变换;
- case 'PAN':
- return 平移;
- case 'DESCRIBE':
- return 图生文;
- case 'BLEND':
- return 图混合;
- case 'SHORTEN':
- return 缩词;
- case 'REROLL':
- return 重绘;
- case 'INPAINT':
- return 局部重绘-提交;
- case 'ZOOM':
- return 变焦;
- case 'CUSTOM_ZOOM':
- return 自定义变焦-提交;
- case 'MODAL':
- return 窗口处理;
- case 'SWAP_FACE':
- return 换脸;
- default:
- return 未知;
- }
+ switch (type) {
+ case 'IMAGINE':
+ return 绘图;
+ case 'UPSCALE':
+ return 放大;
+ case 'VARIATION':
+ return 变换;
+ case 'HIGH_VARIATION':
+ return 强变换;
+ case 'LOW_VARIATION':
+ return 弱变换;
+ case 'PAN':
+ return 平移;
+ case 'DESCRIBE':
+ return 图生文;
+ case 'BLEND':
+ return 图混合;
+ case 'SHORTEN':
+ return 缩词;
+ case 'REROLL':
+ return 重绘;
+ case 'INPAINT':
+ return 局部重绘-提交;
+ case 'ZOOM':
+ return 变焦;
+ case 'CUSTOM_ZOOM':
+ return 自定义变焦-提交;
+ case 'MODAL':
+ return 窗口处理;
+ case 'SWAP_FACE':
+ return 换脸;
+ default:
+ return 未知;
+ }
}
function renderCode(code) {
- switch (code) {
- case 1:
- return 已提交;
- case 21:
- return 等待中;
- case 22:
- return 重复提交;
- case 0:
- return 未提交;
- default:
- return 未知;
- }
+ switch (code) {
+ case 1:
+ return 已提交;
+ case 21:
+ return 等待中;
+ case 22:
+ return 重复提交;
+ case 0:
+ return 未提交;
+ default:
+ return 未知;
+ }
}
function renderStatus(type) {
- // Ensure all cases are string literals by adding quotes.
- switch (type) {
- case 'SUCCESS':
- return 成功;
- case 'NOT_START':
- return 未启动;
- case 'SUBMITTED':
- return 队列中;
- case 'IN_PROGRESS':
- return 执行中;
- case 'FAILURE':
- return 失败;
- case 'MODAL':
- return 窗口等待;
- default:
- return 未知;
- }
+ // Ensure all cases are string literals by adding quotes.
+ switch (type) {
+ case 'SUCCESS':
+ return 成功;
+ case 'NOT_START':
+ return 未启动;
+ case 'SUBMITTED':
+ return 队列中;
+ case 'IN_PROGRESS':
+ return 执行中;
+ case 'FAILURE':
+ return 失败;
+ case 'MODAL':
+ return 窗口等待;
+ default:
+ return 未知;
+ }
}
const renderTimestamp = (timestampInSeconds) => {
- const date = new Date(timestampInSeconds * 1000); // 从秒转换为毫秒
+ const date = new Date(timestampInSeconds * 1000); // 从秒转换为毫秒
- const year = date.getFullYear(); // 获取年份
- const month = ('0' + (date.getMonth() + 1)).slice(-2); // 获取月份,从0开始需要+1,并保证两位数
- const day = ('0' + date.getDate()).slice(-2); // 获取日期,并保证两位数
- const hours = ('0' + date.getHours()).slice(-2); // 获取小时,并保证两位数
- const minutes = ('0' + date.getMinutes()).slice(-2); // 获取分钟,并保证两位数
- const seconds = ('0' + date.getSeconds()).slice(-2); // 获取秒钟,并保证两位数
+ const year = date.getFullYear(); // 获取年份
+ const month = ('0' + (date.getMonth() + 1)).slice(-2); // 获取月份,从0开始需要+1,并保证两位数
+ const day = ('0' + date.getDate()).slice(-2); // 获取日期,并保证两位数
+ const hours = ('0' + date.getHours()).slice(-2); // 获取小时,并保证两位数
+ const minutes = ('0' + date.getMinutes()).slice(-2); // 获取分钟,并保证两位数
+ const seconds = ('0' + date.getSeconds()).slice(-2); // 获取秒钟,并保证两位数
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出
};
const LogsTable = () => {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [modalContent, setModalContent] = useState('');
- const columns = [
- {
- title: '提交时间',
- dataIndex: 'submit_time',
- render: (text, record, index) => {
- return (
-
- {renderTimestamp(text / 1000)}
-
- );
- },
- },
- {
- title: '渠道',
- dataIndex: 'channel_id',
- className: isAdmin() ? 'tableShow' : 'tableHiddle',
- render: (text, record, index) => {
- return (
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [modalContent, setModalContent] = useState('');
+ const columns = [
+ {
+ title: '提交时间',
+ dataIndex: 'submit_time',
+ render: (text, record, index) => {
+ return (
+
+ {renderTimestamp(text / 1000)}
+
+ );
+ }
+ },
+ {
+ title: '渠道',
+ dataIndex: 'channel_id',
+ className: isAdmin() ? 'tableShow' : 'tableHiddle',
+ render: (text, record, index) => {
+ return (
-
- {
- copyText(text); // 假设copyText是用于文本复制的函数
- }}> {text}
-
+
+ {
+ copyText(text); // 假设copyText是用于文本复制的函数
+ }}> {text}
+
- );
- },
- },
- {
- title: '类型',
- dataIndex: 'action',
- render: (text, record, index) => {
- return (
-
- {renderType(text)}
-
- );
- },
- },
- {
- title: '任务ID',
- dataIndex: 'mj_id',
- render: (text, record, index) => {
- return (
-
- {text}
-
- );
- },
- },
- {
- title: '提交结果',
- dataIndex: 'code',
- className: isAdmin() ? 'tableShow' : 'tableHiddle',
- render: (text, record, index) => {
- return (
-
- {renderCode(text)}
-
- );
- },
- },
- {
- title: '任务状态',
- dataIndex: 'status',
- className: isAdmin() ? 'tableShow' : 'tableHiddle',
- render: (text, record, index) => {
- return (
-
- {renderStatus(text)}
-
- );
- },
- },
- {
- title: '进度',
- dataIndex: 'progress',
- render: (text, record, index) => {
- return (
-
- {
- // 转换例如100%为数字100,如果text未定义,返回0
-
- }
-
- );
- },
- },
- {
- title: '结果图片',
- dataIndex: 'image_url',
- render: (text, record, index) => {
- if (!text) {
- return '无';
- }
- return (
-
- );
- }
- },
- {
- title: 'Prompt',
- dataIndex: 'prompt',
- render: (text, record, index) => {
- // 如果text未定义,返回替代文本,例如空字符串''或其他
- if (!text) {
- return '无';
- }
-
- return (
- {
- setModalContent(text);
- setIsModalOpen(true);
- }}
- >
- {text}
-
- );
- }
- },
- {
- title: 'PromptEn',
- dataIndex: 'prompt_en',
- render: (text, record, index) => {
- // 如果text未定义,返回替代文本,例如空字符串''或其他
- if (!text) {
- return '无';
- }
-
- return (
- {
- setModalContent(text);
- setIsModalOpen(true);
- }}
- >
- {text}
-
- );
- }
- },
- {
- title: '失败原因',
- dataIndex: 'fail_reason',
- render: (text, record, index) => {
- // 如果text未定义,返回替代文本,例如空字符串''或其他
- if (!text) {
- return '无';
- }
-
- return (
- {
- setModalContent(text);
- setIsModalOpen(true);
- }}
- >
- {text}
-
- );
+ );
+ }
+ },
+ {
+ title: '类型',
+ dataIndex: 'action',
+ render: (text, record, index) => {
+ return (
+
+ {renderType(text)}
+
+ );
+ }
+ },
+ {
+ title: '任务ID',
+ dataIndex: 'mj_id',
+ render: (text, record, index) => {
+ return (
+
+ {text}
+
+ );
+ }
+ },
+ {
+ title: '提交结果',
+ dataIndex: 'code',
+ className: isAdmin() ? 'tableShow' : 'tableHiddle',
+ render: (text, record, index) => {
+ return (
+
+ {renderCode(text)}
+
+ );
+ }
+ },
+ {
+ title: '任务状态',
+ dataIndex: 'status',
+ className: isAdmin() ? 'tableShow' : 'tableHiddle',
+ render: (text, record, index) => {
+ return (
+
+ {renderStatus(text)}
+
+ );
+ }
+ },
+ {
+ title: '进度',
+ dataIndex: 'progress',
+ render: (text, record, index) => {
+ return (
+
+ {
+ // 转换例如100%为数字100,如果text未定义,返回0
+
}
+
+ );
+ }
+ },
+ {
+ title: '结果图片',
+ dataIndex: 'image_url',
+ render: (text, record, index) => {
+ if (!text) {
+ return '无';
+ }
+ return (
+
+ );
+ }
+ },
+ {
+ title: 'Prompt',
+ dataIndex: 'prompt',
+ render: (text, record, index) => {
+ // 如果text未定义,返回替代文本,例如空字符串''或其他
+ if (!text) {
+ return '无';
}
- ];
-
- const [logs, setLogs] = useState([]);
- const [loading, setLoading] = useState(true);
- const [activePage, setActivePage] = useState(1);
- const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
- const [logType, setLogType] = useState(0);
- const isAdminUser = isAdmin();
- const [isModalOpenurl, setIsModalOpenurl] = useState(false);
-
- // 定义模态框图片URL的状态和更新函数
- const [modalImageUrl, setModalImageUrl] = useState('');
- let now = new Date();
- // 初始化start_timestamp为前一天
- const [inputs, setInputs] = useState({
- channel_id: '',
- mj_id: '',
- start_timestamp: timestamp2string(now.getTime() / 1000 - 2592000),
- end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
- });
- const {channel_id, mj_id, start_timestamp, end_timestamp} = inputs;
-
- const [stat, setStat] = useState({
- quota: 0,
- token: 0
- });
-
- const handleInputChange = (value, name) => {
- setInputs((inputs) => ({...inputs, [name]: value}));
- };
-
-
- const setLogsFormat = (logs) => {
- for (let i = 0; i < logs.length; i++) {
- logs[i].timestamp2string = timestamp2string(logs[i].created_at);
- logs[i].key = '' + logs[i].id;
+ return (
+ {
+ setModalContent(text);
+ setIsModalOpen(true);
+ }}
+ >
+ {text}
+
+ );
+ }
+ },
+ {
+ title: 'PromptEn',
+ dataIndex: 'prompt_en',
+ render: (text, record, index) => {
+ // 如果text未定义,返回替代文本,例如空字符串''或其他
+ if (!text) {
+ return '无';
}
- // data.key = '' + data.id
- setLogs(logs);
- setLogCount(logs.length + ITEMS_PER_PAGE);
- // console.log(logCount);
+
+ return (
+ {
+ setModalContent(text);
+ setIsModalOpen(true);
+ }}
+ >
+ {text}
+
+ );
+ }
+ },
+ {
+ title: '失败原因',
+ dataIndex: 'fail_reason',
+ render: (text, record, index) => {
+ // 如果text未定义,返回替代文本,例如空字符串''或其他
+ if (!text) {
+ return '无';
+ }
+
+ return (
+ {
+ setModalContent(text);
+ setIsModalOpen(true);
+ }}
+ >
+ {text}
+
+ );
+ }
}
- const loadLogs = async (startIdx) => {
- setLoading(true);
+ ];
- let url = '';
- let localStartTimestamp = Date.parse(start_timestamp);
- let localEndTimestamp = Date.parse(end_timestamp);
- if (isAdminUser) {
- url = `/api/mj/?p=${startIdx}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
- } else {
- url = `/api/mj/self/?p=${startIdx}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
- }
- const res = await API.get(url);
- const {success, message, data} = res.data;
- if (success) {
- if (startIdx === 0) {
- setLogsFormat(data);
- } else {
- let newLogs = [...logs];
- newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
- setLogsFormat(newLogs);
- }
- } else {
- showError(message);
- }
- setLoading(false);
- };
+ const [logs, setLogs] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [activePage, setActivePage] = useState(1);
+ const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
+ const [logType, setLogType] = useState(0);
+ const isAdminUser = isAdmin();
+ const [isModalOpenurl, setIsModalOpenurl] = useState(false);
+ const [showBanner, setShowBanner] = useState(false);
- const pageData = logs.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE);
+ // 定义模态框图片URL的状态和更新函数
+ const [modalImageUrl, setModalImageUrl] = useState('');
+ let now = new Date();
+ // 初始化start_timestamp为前一天
+ const [inputs, setInputs] = useState({
+ channel_id: '',
+ mj_id: '',
+ start_timestamp: timestamp2string(now.getTime() / 1000 - 2592000),
+ end_timestamp: timestamp2string(now.getTime() / 1000 + 3600)
+ });
+ const { channel_id, mj_id, start_timestamp, end_timestamp } = inputs;
- const handlePageChange = page => {
- setActivePage(page);
- if (page === Math.ceil(logs.length / ITEMS_PER_PAGE) + 1) {
- // In this case we have to load more data and then append them.
- loadLogs(page - 1).then(r => {
- });
- }
- };
+ const [stat, setStat] = useState({
+ quota: 0,
+ token: 0
+ });
- const refresh = async () => {
- // setLoading(true);
- setActivePage(1);
- await loadLogs(0);
- };
+ const handleInputChange = (value, name) => {
+ setInputs((inputs) => ({ ...inputs, [name]: value }));
+ };
- const copyText = async (text) => {
- if (await copy(text)) {
- showSuccess('已复制:' + text);
- } else {
- // setSearchKeyword(text);
- Modal.error({title: '无法复制到剪贴板,请手动复制', content: text});
- }
+
+ const setLogsFormat = (logs) => {
+ for (let i = 0; i < logs.length; i++) {
+ logs[i].timestamp2string = timestamp2string(logs[i].created_at);
+ logs[i].key = '' + logs[i].id;
}
+ // data.key = '' + data.id
+ setLogs(logs);
+ setLogCount(logs.length + ITEMS_PER_PAGE);
+ // console.log(logCount);
+ };
- useEffect(() => {
- refresh().then();
- }, [logType]);
+ const loadLogs = async (startIdx) => {
+ setLoading(true);
+ let url = '';
+ let localStartTimestamp = Date.parse(start_timestamp);
+ let localEndTimestamp = Date.parse(end_timestamp);
+ if (isAdminUser) {
+ url = `/api/mj/?p=${startIdx}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
+ } else {
+ url = `/api/mj/self/?p=${startIdx}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
+ }
+ const res = await API.get(url);
+ const { success, message, data } = res.data;
+ if (success) {
+ if (startIdx === 0) {
+ setLogsFormat(data);
+ } else {
+ let newLogs = [...logs];
+ newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
+ setLogsFormat(newLogs);
+ }
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
- return (
- <>
+ const pageData = logs.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE);
-
- handleInputChange(value, 'channel_id')}/>
- handleInputChange(value, 'mj_id')}/>
- handleInputChange(value, 'start_timestamp')}/>
- handleInputChange(value, 'end_timestamp')}/>
+ const handlePageChange = page => {
+ setActivePage(page);
+ if (page === Math.ceil(logs.length / ITEMS_PER_PAGE) + 1) {
+ // In this case we have to load more data and then append them.
+ loadLogs(page - 1).then(r => {
+ });
+ }
+ };
-
-
-
- >
-
-
- setIsModalOpen(false)}
- onCancel={() => setIsModalOpen(false)}
- closable={null}
- bodyStyle={{height: '400px', overflow: 'auto'}} // 设置模态框内容区域样式
- width={800} // 设置模态框宽度
- >
- {modalContent}
-
- setIsModalOpenurl(visible)}
- />
+ const refresh = async () => {
+ // setLoading(true);
+ setActivePage(1);
+ await loadLogs(0);
+ };
-
- >
- );
+ const copyText = async (text) => {
+ if (await copy(text)) {
+ showSuccess('已复制:' + text);
+ } else {
+ // setSearchKeyword(text);
+ Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
+ }
+ };
+
+ useEffect(() => {
+ refresh().then();
+ }, [logType]);
+
+ useEffect(() => {
+ const mjNotifyEnabled = localStorage.getItem('mj_notify_enabled');
+ if (mjNotifyEnabled !== 'true') {
+ setShowBanner(true);
+ }
+ }, []);
+
+ return (
+ <>
+
+
+ {isAdminUser && showBanner ? : <>>
+ }
+ handleInputChange(value, 'channel_id')} />
+ handleInputChange(value, 'mj_id')} />
+ handleInputChange(value, 'start_timestamp')} />
+ handleInputChange(value, 'end_timestamp')} />
+
+
+
+
+ >
+
+
+ setIsModalOpen(false)}
+ onCancel={() => setIsModalOpen(false)}
+ closable={null}
+ bodyStyle={{ height: '400px', overflow: 'auto' }} // 设置模态框内容区域样式
+ width={800} // 设置模态框宽度
+ >
+ {modalContent}
+
+ setIsModalOpenurl(visible)}
+ />
+
+
+ >
+ );
};
export default LogsTable;
diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js
index 1a3a4be..874c552 100644
--- a/web/src/components/OperationSetting.js
+++ b/web/src/components/OperationSetting.js
@@ -1,453 +1,468 @@
-import React, {useEffect, useState} from 'react';
-import {Divider, Form, Grid, Header} from 'semantic-ui-react';
-import {API, showError, showSuccess, timestamp2string, verifyJSON} from '../helpers';
+import React, { useEffect, useState } from 'react';
+import { Divider, Form, Grid, Header } from 'semantic-ui-react';
+import { API, showError, showSuccess, timestamp2string, verifyJSON } from '../helpers';
const OperationSetting = () => {
- let now = new Date();
- let [inputs, setInputs] = useState({
- QuotaForNewUser: 0,
- QuotaForInviter: 0,
- QuotaForInvitee: 0,
- QuotaRemindThreshold: 0,
- PreConsumedQuota: 0,
- ModelRatio: '',
- ModelPrice: '',
- GroupRatio: '',
- TopUpLink: '',
- ChatLink: '',
- ChatLink2: '', // 添加的新状态变量
- QuotaPerUnit: 0,
- AutomaticDisableChannelEnabled: '',
- AutomaticEnableChannelEnabled: '',
- ChannelDisableThreshold: 0,
- LogConsumeEnabled: '',
- DisplayInCurrencyEnabled: '',
- DisplayTokenStatEnabled: '',
- DrawingEnabled: '',
- DataExportEnabled: '',
- DataExportDefaultTime: 'hour',
- DataExportInterval: 5,
- DefaultCollapseSidebar: '', // 默认折叠侧边栏
- RetryTimes: 0
+ let now = new Date();
+ let [inputs, setInputs] = useState({
+ QuotaForNewUser: 0,
+ QuotaForInviter: 0,
+ QuotaForInvitee: 0,
+ QuotaRemindThreshold: 0,
+ PreConsumedQuota: 0,
+ ModelRatio: '',
+ ModelPrice: '',
+ GroupRatio: '',
+ TopUpLink: '',
+ ChatLink: '',
+ ChatLink2: '', // 添加的新状态变量
+ QuotaPerUnit: 0,
+ AutomaticDisableChannelEnabled: '',
+ AutomaticEnableChannelEnabled: '',
+ ChannelDisableThreshold: 0,
+ LogConsumeEnabled: '',
+ DisplayInCurrencyEnabled: '',
+ DisplayTokenStatEnabled: '',
+ MjNotifyEnabled: '',
+ DrawingEnabled: '',
+ DataExportEnabled: '',
+ DataExportDefaultTime: 'hour',
+ DataExportInterval: 5,
+ DefaultCollapseSidebar: '', // 默认折叠侧边栏
+ RetryTimes: 0
+ });
+ const [originInputs, setOriginInputs] = useState({});
+ let [loading, setLoading] = useState(false);
+ let [historyTimestamp, setHistoryTimestamp] = useState(timestamp2string(now.getTime() / 1000 - 30 * 24 * 3600)); // a month ago
+ // 精确时间选项(小时,天,周)
+ const timeOptions = [
+ { key: 'hour', text: '小时', value: 'hour' },
+ { key: 'day', text: '天', value: 'day' },
+ { key: 'week', text: '周', value: 'week' }
+ ];
+ const getOptions = async () => {
+ const res = await API.get('/api/option/');
+ const { success, message, data } = res.data;
+ if (success) {
+ let newInputs = {};
+ data.forEach((item) => {
+ if (item.key === 'ModelRatio' || item.key === 'GroupRatio' || item.key === 'ModelPrice') {
+ item.value = JSON.stringify(JSON.parse(item.value), null, 2);
+ }
+ newInputs[item.key] = item.value;
+ });
+ setInputs(newInputs);
+ setOriginInputs(newInputs);
+ } else {
+ showError(message);
+ }
+ };
+
+ useEffect(() => {
+ getOptions().then();
+ }, []);
+
+ const updateOption = async (key, value) => {
+ setLoading(true);
+ if (key.endsWith('Enabled')) {
+ value = inputs[key] === 'true' ? 'false' : 'true';
+ }
+ if (key === 'DefaultCollapseSidebar') {
+ value = inputs[key] === 'true' ? 'false' : 'true';
+ }
+ console.log(key, value);
+ const res = await API.put('/api/option/', {
+ key,
+ value
});
- const [originInputs, setOriginInputs] = useState({});
- let [loading, setLoading] = useState(false);
- let [historyTimestamp, setHistoryTimestamp] = useState(timestamp2string(now.getTime() / 1000 - 30 * 24 * 3600)); // a month ago
- // 精确时间选项(小时,天,周)
- const timeOptions = [
- {key: 'hour', text: '小时', value: 'hour'},
- {key: 'day', text: '天', value: 'day'},
- {key: 'week', text: '周', value: 'week'}
- ];
- const getOptions = async () => {
- const res = await API.get('/api/option/');
- const {success, message, data} = res.data;
- if (success) {
- let newInputs = {};
- data.forEach((item) => {
- if (item.key === 'ModelRatio' || item.key === 'GroupRatio' || item.key === 'ModelPrice') {
- item.value = JSON.stringify(JSON.parse(item.value), null, 2);
- }
- newInputs[item.key] = item.value;
- });
- setInputs(newInputs);
- setOriginInputs(newInputs);
- } else {
- showError(message);
- }
- };
+ const { success, message } = res.data;
+ if (success) {
+ setInputs((inputs) => ({ ...inputs, [key]: value }));
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
- useEffect(() => {
- getOptions().then();
- }, []);
+ const handleInputChange = async (e, { name, value }) => {
+ if (name.endsWith('Enabled') || name === 'DataExportInterval' || name === 'DataExportDefaultTime' || name === 'DefaultCollapseSidebar') {
+ if (name === 'DataExportDefaultTime') {
+ localStorage.setItem('data_export_default_time', value);
+ } else if (name === 'MjNotifyEnabled') {
+ localStorage.setItem('mj_notify_enabled', value);
+ }
+ await updateOption(name, value);
+ } else {
+ setInputs((inputs) => ({ ...inputs, [name]: value }));
+ }
+ };
- const updateOption = async (key, value) => {
- setLoading(true);
- if (key.endsWith('Enabled')) {
- value = inputs[key] === 'true' ? 'false' : 'true';
+ const submitConfig = async (group) => {
+ switch (group) {
+ case 'monitor':
+ if (originInputs['ChannelDisableThreshold'] !== inputs.ChannelDisableThreshold) {
+ await updateOption('ChannelDisableThreshold', inputs.ChannelDisableThreshold);
}
- if (key === 'DefaultCollapseSidebar') {
- value = inputs[key] === 'true' ? 'false' : 'true';
+ if (originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold) {
+ await updateOption('QuotaRemindThreshold', inputs.QuotaRemindThreshold);
}
- console.log(key, value)
- const res = await API.put('/api/option/', {
- key,
- value
- });
- const {success, message} = res.data;
- if (success) {
- setInputs((inputs) => ({...inputs, [key]: value}));
- } else {
- showError(message);
- }
- setLoading(false);
- };
-
- const handleInputChange = async (e, {name, value}) => {
- if (name.endsWith('Enabled') || name === 'DataExportInterval' || name === 'DataExportDefaultTime' || name === 'DefaultCollapseSidebar') {
- if (name === 'DataExportDefaultTime') {
- localStorage.setItem('data_export_default_time', value);
- }
- await updateOption(name, value);
- } else {
- setInputs((inputs) => ({...inputs, [name]: value}));
- }
- };
-
- const submitConfig = async (group) => {
- switch (group) {
- case 'monitor':
- if (originInputs['ChannelDisableThreshold'] !== inputs.ChannelDisableThreshold) {
- await updateOption('ChannelDisableThreshold', inputs.ChannelDisableThreshold);
- }
- if (originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold) {
- await updateOption('QuotaRemindThreshold', inputs.QuotaRemindThreshold);
- }
- break;
- case 'ratio':
- if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
- if (!verifyJSON(inputs.ModelRatio)) {
- showError('模型倍率不是合法的 JSON 字符串');
- return;
- }
- await updateOption('ModelRatio', inputs.ModelRatio);
- }
- if (originInputs['GroupRatio'] !== inputs.GroupRatio) {
- if (!verifyJSON(inputs.GroupRatio)) {
- showError('分组倍率不是合法的 JSON 字符串');
- return;
- }
- await updateOption('GroupRatio', inputs.GroupRatio);
- }
- if (originInputs['ModelPrice'] !== inputs.ModelPrice) {
- if (!verifyJSON(inputs.ModelPrice)) {
- showError('模型固定价格不是合法的 JSON 字符串');
- return;
- }
- await updateOption('ModelPrice', inputs.ModelPrice);
- }
- break;
- case 'quota':
- if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) {
- await updateOption('QuotaForNewUser', inputs.QuotaForNewUser);
- }
- if (originInputs['QuotaForInvitee'] !== inputs.QuotaForInvitee) {
- await updateOption('QuotaForInvitee', inputs.QuotaForInvitee);
- }
- if (originInputs['QuotaForInviter'] !== inputs.QuotaForInviter) {
- await updateOption('QuotaForInviter', inputs.QuotaForInviter);
- }
- if (originInputs['PreConsumedQuota'] !== inputs.PreConsumedQuota) {
- await updateOption('PreConsumedQuota', inputs.PreConsumedQuota);
- }
- break;
- case 'general':
- if (originInputs['TopUpLink'] !== inputs.TopUpLink) {
- await updateOption('TopUpLink', inputs.TopUpLink);
- }
- if (originInputs['ChatLink'] !== inputs.ChatLink) {
- await updateOption('ChatLink', inputs.ChatLink);
- }
- if (originInputs['ChatLink2'] !== inputs.ChatLink2) {
- await updateOption('ChatLink2', inputs.ChatLink2);
- }
- if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) {
- await updateOption('QuotaPerUnit', inputs.QuotaPerUnit);
- }
- if (originInputs['RetryTimes'] !== inputs.RetryTimes) {
- await updateOption('RetryTimes', inputs.RetryTimes);
- }
- break;
- }
- };
-
- const deleteHistoryLogs = async () => {
- console.log(inputs);
- const res = await API.delete(`/api/log/?target_timestamp=${Date.parse(historyTimestamp) / 1000}`);
- const {success, message, data} = res.data;
- if (success) {
- showSuccess(`${data} 条日志已清理!`);
+ break;
+ case 'ratio':
+ if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
+ if (!verifyJSON(inputs.ModelRatio)) {
+ showError('模型倍率不是合法的 JSON 字符串');
return;
+ }
+ await updateOption('ModelRatio', inputs.ModelRatio);
}
- showError('日志清理失败:' + message);
- };
- return (
-
-
-
-
-
-
-
-
-
-
+ if (originInputs['GroupRatio'] !== inputs.GroupRatio) {
+ if (!verifyJSON(inputs.GroupRatio)) {
+ showError('分组倍率不是合法的 JSON 字符串');
+ return;
+ }
+ await updateOption('GroupRatio', inputs.GroupRatio);
+ }
+ if (originInputs['ModelPrice'] !== inputs.ModelPrice) {
+ if (!verifyJSON(inputs.ModelPrice)) {
+ showError('模型固定价格不是合法的 JSON 字符串');
+ return;
+ }
+ await updateOption('ModelPrice', inputs.ModelPrice);
+ }
+ break;
+ case 'quota':
+ if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) {
+ await updateOption('QuotaForNewUser', inputs.QuotaForNewUser);
+ }
+ if (originInputs['QuotaForInvitee'] !== inputs.QuotaForInvitee) {
+ await updateOption('QuotaForInvitee', inputs.QuotaForInvitee);
+ }
+ if (originInputs['QuotaForInviter'] !== inputs.QuotaForInviter) {
+ await updateOption('QuotaForInviter', inputs.QuotaForInviter);
+ }
+ if (originInputs['PreConsumedQuota'] !== inputs.PreConsumedQuota) {
+ await updateOption('PreConsumedQuota', inputs.PreConsumedQuota);
+ }
+ break;
+ case 'general':
+ if (originInputs['TopUpLink'] !== inputs.TopUpLink) {
+ await updateOption('TopUpLink', inputs.TopUpLink);
+ }
+ if (originInputs['ChatLink'] !== inputs.ChatLink) {
+ await updateOption('ChatLink', inputs.ChatLink);
+ }
+ if (originInputs['ChatLink2'] !== inputs.ChatLink2) {
+ await updateOption('ChatLink2', inputs.ChatLink2);
+ }
+ if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) {
+ await updateOption('QuotaPerUnit', inputs.QuotaPerUnit);
+ }
+ if (originInputs['RetryTimes'] !== inputs.RetryTimes) {
+ await updateOption('RetryTimes', inputs.RetryTimes);
+ }
+ break;
+ }
+ };
-
-
-
-
-
- {
- submitConfig('general').then();
- }}>保存通用设置
-
-
-
-
-
- {
- setHistoryTimestamp(value);
- }}/>
-
- {
- deleteHistoryLogs().then();
- }}>清理历史日志
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- submitConfig('monitor').then();
- }}>保存监控设置
-
-
-
-
-
-
-
-
- {
- submitConfig('quota').then();
- }}>保存额度设置
-
-
-
-
-
-
-
-
-
-
-
- {
- submitConfig('ratio').then();
- }}>保存倍率设置
-
-
-
- )
- ;
+ const deleteHistoryLogs = async () => {
+ console.log(inputs);
+ const res = await API.delete(`/api/log/?target_timestamp=${Date.parse(historyTimestamp) / 1000}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ showSuccess(`${data} 条日志已清理!`);
+ return;
+ }
+ showError('日志清理失败:' + message);
+ };
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ submitConfig('general').then();
+ }}>保存通用设置
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ setHistoryTimestamp(value);
+ }} />
+
+ {
+ deleteHistoryLogs().then();
+ }}>清理历史日志
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ submitConfig('monitor').then();
+ }}>保存监控设置
+
+
+
+
+
+
+
+
+ {
+ submitConfig('quota').then();
+ }}>保存额度设置
+
+
+
+
+
+
+
+
+
+
+
+ {
+ submitConfig('ratio').then();
+ }}>保存倍率设置
+
+
+
+ )
+ ;
};
export default OperationSetting;
diff --git a/web/src/components/OtherSetting.js b/web/src/components/OtherSetting.js
index 878c2b0..014492d 100644
--- a/web/src/components/OtherSetting.js
+++ b/web/src/components/OtherSetting.js
@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
-import { Col, Row , Form, Button, Banner } from '@douyinfe/semi-ui';
+import { Banner, Button, Col, Form, Row } from '@douyinfe/semi-ui';
import { API, showError, showSuccess } from '../helpers';
import { marked } from 'marked';
@@ -57,8 +57,8 @@ const OtherSetting = () => {
await updateOption('Notice', inputs.Notice);
showSuccess('公告已更新');
} catch (error) {
- console.error("公告更新失败", error);
- showError("公告更新失败")
+ console.error('公告更新失败', error);
+ showError('公告更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false }));
}
@@ -72,8 +72,8 @@ const OtherSetting = () => {
await updateOption('SystemName', inputs.SystemName);
showSuccess('系统名称已更新');
} catch (error) {
- console.error("系统名称更新失败", error);
- showError("系统名称更新失败")
+ console.error('系统名称更新失败', error);
+ showError('系统名称更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, SystemName: false }));
}
@@ -86,8 +86,8 @@ const OtherSetting = () => {
await updateOption('Logo', inputs.Logo);
showSuccess('Logo 已更新');
} catch (error) {
- console.error("Logo 更新失败", error);
- showError("Logo 更新失败")
+ console.error('Logo 更新失败', error);
+ showError('Logo 更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: false }));
}
@@ -99,8 +99,8 @@ const OtherSetting = () => {
await updateOption(key, inputs[key]);
showSuccess('首页内容已更新');
} catch (error) {
- console.error("首页内容更新失败", error);
- showError("首页内容更新失败")
+ console.error('首页内容更新失败', error);
+ showError('首页内容更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, HomePageContent: false }));
}
@@ -112,8 +112,8 @@ const OtherSetting = () => {
await updateOption('About', inputs.About);
showSuccess('关于内容已更新');
} catch (error) {
- console.error("关于内容更新失败", error);
- showError("关于内容更新失败");
+ console.error('关于内容更新失败', error);
+ showError('关于内容更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, About: false }));
}
@@ -125,16 +125,14 @@ const OtherSetting = () => {
await updateOption('Footer', inputs.Footer);
showSuccess('页脚内容已更新');
} catch (error) {
- console.error("页脚内容更新失败", error);
- showError("页脚内容更新失败");
+ console.error('页脚内容更新失败', error);
+ showError('页脚内容更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: false }));
}
};
-
-
const openGitHubRelease = () => {
window.location =
'https://github.com/songquanpeng/one-api/releases/latest';
@@ -173,16 +171,17 @@ const OtherSetting = () => {
}
};
- useEffect( () => {
+ useEffect(() => {
getOptions();
}, []);
return (
-
+
{/* 通用设置 */}
-
{
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
- />
+ />
{/* 个性化设置 */}
-
+ />
+ />
{
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
- />
-
+ />
+
{
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
- />
+ />
{/* */}
{
type="info"
description="移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。"
closeIcon={null}
- style={{ marginTop: 15 }}
+ style={{ marginTop: 15 }}
/>
+ />
@@ -270,7 +271,7 @@ const OtherSetting = () => {
{/* />*/}
{/* */}
{/**/}
-
+
);
};
diff --git a/web/src/components/PasswordResetConfirm.js b/web/src/components/PasswordResetConfirm.js
index d82ae89..071837a 100644
--- a/web/src/components/PasswordResetConfirm.js
+++ b/web/src/components/PasswordResetConfirm.js
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-react';
-import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers';
+import { API, copy, showError, showNotice } from '../helpers';
import { useSearchParams } from 'react-router-dom';
const PasswordResetConfirm = () => {
const [inputs, setInputs] = useState({
email: '',
- token: '',
+ token: ''
});
const { email, token } = inputs;
@@ -23,7 +23,7 @@ const PasswordResetConfirm = () => {
let email = searchParams.get('email');
setInputs({
token,
- email,
+ email
});
}, []);
@@ -37,7 +37,7 @@ const PasswordResetConfirm = () => {
setDisableButton(false);
setCountdown(30);
}
- return () => clearInterval(countdownInterval);
+ return () => clearInterval(countdownInterval);
}, [disableButton, countdown]);
async function handleSubmit(e) {
@@ -46,7 +46,7 @@ const PasswordResetConfirm = () => {
setLoading(true);
const res = await API.post(`/api/user/reset`, {
email,
- token,
+ token
});
const { success, message } = res.data;
if (success) {
@@ -59,44 +59,44 @@ const PasswordResetConfirm = () => {
}
setLoading(false);
}
-
+
return (
-
+
-
- 密码重置确认
+
-
- );
+ );
};
export default PasswordResetConfirm;
diff --git a/web/src/components/PasswordResetForm.js b/web/src/components/PasswordResetForm.js
index f3610d3..ff3eaad 100644
--- a/web/src/components/PasswordResetForm.js
+++ b/web/src/components/PasswordResetForm.js
@@ -56,19 +56,19 @@ const PasswordResetForm = () => {
}
return (
-
+
-
- 密码重置
+
-
+
+ `第 ${page.currentStart} - ${page.currentEnd} 条,共 ${redemptions.length} 条`,
+ // onPageSizeChange: (size) => {
+ // setPageSize(size);
+ // setActivePage(1);
+ // },
+ onPageChange: handlePageChange
+ }} loading={loading} rowSelection={rowSelection} onRow={handleRow}>
+
+
+
+ >
+ );
};
export default RedemptionsTable;
diff --git a/web/src/components/RegisterForm.js b/web/src/components/RegisterForm.js
index f91d6da..1f26b63 100644
--- a/web/src/components/RegisterForm.js
+++ b/web/src/components/RegisterForm.js
@@ -98,49 +98,49 @@ const RegisterForm = () => {
};
return (
-
+
-
+
-
+
{showEmailVerification ? (
<>
获取验证码
@@ -149,11 +149,11 @@ const RegisterForm = () => {
/>
>
) : (
@@ -170,9 +170,9 @@ const RegisterForm = () => {
<>>
)}
已有账户?
-
+
点击登录
diff --git a/web/src/components/SiderBar.js b/web/src/components/SiderBar.js
index af68aa2..78120aa 100644
--- a/web/src/components/SiderBar.js
+++ b/web/src/components/SiderBar.js
@@ -1,213 +1,220 @@
-import React, { useContext, useEffect, useLayoutEffect, useMemo, useState } from 'react';
-import {Link, useNavigate} from 'react-router-dom';
-import {UserContext} from '../context/User';
+import React, { useContext, useEffect, useMemo, useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { UserContext } from '../context/User';
import { StatusContext } from '../context/Status';
-import { API, getLogo, getSystemName, isAdmin, isMobile, showError, showSuccess } from '../helpers';
+import { API, getLogo, getSystemName, isAdmin, isMobile, showError } from '../helpers';
import '../index.css';
import {
- IconCalendarClock,
- IconHistogram,
- IconGift,
- IconKey,
- IconUser,
- IconLayers,
- IconSetting,
- IconCreditCard,
- IconComment,
- IconHome,
- IconImage
+ IconCalendarClock,
+ IconComment,
+ IconCreditCard,
+ IconGift,
+ IconHistogram,
+ IconHome,
+ IconImage,
+ IconKey,
+ IconLayers,
+ IconSetting,
+ IconUser
} from '@douyinfe/semi-icons';
-import {Nav, Avatar, Dropdown, Layout} from '@douyinfe/semi-ui';
+import { Layout, Nav } from '@douyinfe/semi-ui';
// HeaderBar Buttons
const SiderBar = () => {
- const [userState, userDispatch] = useContext(UserContext);
- const [statusState, statusDispatch] = useContext(StatusContext);
- const defaultIsCollapsed = isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true';
+ const [userState, userDispatch] = useContext(UserContext);
+ const [statusState, statusDispatch] = useContext(StatusContext);
+ const defaultIsCollapsed = isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true';
- let navigate = useNavigate();
- const [selectedKeys, setSelectedKeys] = useState(['home']);
- const systemName = getSystemName();
- const logo = getLogo();
- const [isCollapsed, setIsCollapsed] = useState(defaultIsCollapsed);
+ let navigate = useNavigate();
+ const [selectedKeys, setSelectedKeys] = useState(['home']);
+ const systemName = getSystemName();
+ const logo = getLogo();
+ const [isCollapsed, setIsCollapsed] = useState(defaultIsCollapsed);
- const headerButtons = useMemo(() => [
- {
- text: '首页',
- itemKey: 'home',
- to: '/',
- icon:
- },
- {
- text: '渠道',
- itemKey: 'channel',
- to: '/channel',
- icon: ,
- className: isAdmin()?'semi-navigation-item-normal':'tableHiddle',
- },
- {
- text: '聊天',
- itemKey: 'chat',
- to: '/chat',
- icon: ,
- className: localStorage.getItem('chat_link')?'semi-navigation-item-normal':'tableHiddle',
- },
- {
- text: '令牌',
- itemKey: 'token',
- to: '/token',
- icon:
- },
- {
- text: '兑换码',
- itemKey: 'redemption',
- to: '/redemption',
- icon: ,
- className: isAdmin()?'semi-navigation-item-normal':'tableHiddle',
- },
- {
- text: '钱包',
- itemKey: 'topup',
- to: '/topup',
- icon:
- },
- {
- text: '用户管理',
- itemKey: 'user',
- to: '/user',
- icon: ,
- className: isAdmin()?'semi-navigation-item-normal':'tableHiddle',
- },
- {
- text: '日志',
- itemKey: 'log',
- to: '/log',
- icon:
- },
- {
- text: '数据看板',
- itemKey: 'detail',
- to: '/detail',
- icon: ,
- className: localStorage.getItem('enable_data_export') === 'true'?'semi-navigation-item-normal':'tableHiddle',
- },
- {
- text: '绘图',
- itemKey: 'midjourney',
- to: '/midjourney',
- icon: ,
- className: localStorage.getItem('enable_drawing') === 'true'?'semi-navigation-item-normal':'tableHiddle',
- },
- {
- text: '设置',
- itemKey: 'setting',
- to: '/setting',
- icon:
- },
- // {
- // text: '关于',
- // itemKey: 'about',
- // to: '/about',
- // icon:
- // }
- ], [localStorage.getItem('enable_data_export'), localStorage.getItem('enable_drawing'), localStorage.getItem('chat_link'), isAdmin()]);
+ const routerMap = {
+ home: '/',
+ channel: '/channel',
+ token: '/token',
+ redemption: '/redemption',
+ topup: '/topup',
+ user: '/user',
+ log: '/log',
+ midjourney: '/midjourney',
+ setting: '/setting',
+ about: '/about',
+ chat: '/chat',
+ detail: '/detail'
+ };
- const loadStatus = async () => {
- const res = await API.get('/api/status');
- const { success, data } = res.data;
- if (success) {
- localStorage.setItem('status', JSON.stringify(data));
- statusDispatch({ type: 'set', payload: data });
- localStorage.setItem('system_name', data.system_name);
- localStorage.setItem('logo', data.logo);
- localStorage.setItem('footer_html', data.footer_html);
- localStorage.setItem('quota_per_unit', data.quota_per_unit);
- localStorage.setItem('display_in_currency', data.display_in_currency);
- localStorage.setItem('enable_drawing', data.enable_drawing);
- localStorage.setItem('enable_data_export', data.enable_data_export);
- localStorage.setItem('data_export_default_time', data.data_export_default_time);
- localStorage.setItem('default_collapse_sidebar', data.default_collapse_sidebar);
- if (data.chat_link) {
- localStorage.setItem('chat_link', data.chat_link);
- } else {
- localStorage.removeItem('chat_link');
- }
- if (data.chat_link2) {
- localStorage.setItem('chat_link2', data.chat_link2);
- } else {
- localStorage.removeItem('chat_link2');
- }
- } else {
- showError('无法正常连接至服务器!');
- }
- };
+ const headerButtons = useMemo(() => [
+ {
+ text: '首页',
+ itemKey: 'home',
+ to: '/',
+ icon:
+ },
+ {
+ text: '渠道',
+ itemKey: 'channel',
+ to: '/channel',
+ icon: ,
+ className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle'
+ },
+ {
+ text: '聊天',
+ itemKey: 'chat',
+ to: '/chat',
+ icon: ,
+ className: localStorage.getItem('chat_link') ? 'semi-navigation-item-normal' : 'tableHiddle'
+ },
+ {
+ text: '令牌',
+ itemKey: 'token',
+ to: '/token',
+ icon:
+ },
+ {
+ text: '兑换码',
+ itemKey: 'redemption',
+ to: '/redemption',
+ icon: ,
+ className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle'
+ },
+ {
+ text: '钱包',
+ itemKey: 'topup',
+ to: '/topup',
+ icon:
+ },
+ {
+ text: '用户管理',
+ itemKey: 'user',
+ to: '/user',
+ icon: ,
+ className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle'
+ },
+ {
+ text: '日志',
+ itemKey: 'log',
+ to: '/log',
+ icon:
+ },
+ {
+ text: '数据看板',
+ itemKey: 'detail',
+ to: '/detail',
+ icon: ,
+ className: localStorage.getItem('enable_data_export') === 'true' ? 'semi-navigation-item-normal' : 'tableHiddle'
+ },
+ {
+ text: '绘图',
+ itemKey: 'midjourney',
+ to: '/midjourney',
+ icon: ,
+ className: localStorage.getItem('enable_drawing') === 'true' ? 'semi-navigation-item-normal' : 'tableHiddle'
+ },
+ {
+ text: '设置',
+ itemKey: 'setting',
+ to: '/setting',
+ icon:
+ }
+ // {
+ // text: '关于',
+ // itemKey: 'about',
+ // to: '/about',
+ // icon:
+ // }
+ ], [localStorage.getItem('enable_data_export'), localStorage.getItem('enable_drawing'), localStorage.getItem('chat_link'), isAdmin()]);
- useEffect(() => {
- loadStatus().then(() => {
- setIsCollapsed(isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true');
- });
- },[])
+ const loadStatus = async () => {
+ const res = await API.get('/api/status');
+ const { success, data } = res.data;
+ if (success) {
+ localStorage.setItem('status', JSON.stringify(data));
+ statusDispatch({ type: 'set', payload: data });
+ localStorage.setItem('system_name', data.system_name);
+ localStorage.setItem('logo', data.logo);
+ localStorage.setItem('footer_html', data.footer_html);
+ localStorage.setItem('quota_per_unit', data.quota_per_unit);
+ localStorage.setItem('display_in_currency', data.display_in_currency);
+ localStorage.setItem('enable_drawing', data.enable_drawing);
+ localStorage.setItem('enable_data_export', data.enable_data_export);
+ localStorage.setItem('data_export_default_time', data.data_export_default_time);
+ localStorage.setItem('default_collapse_sidebar', data.default_collapse_sidebar);
+ localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled);
+ if (data.chat_link) {
+ localStorage.setItem('chat_link', data.chat_link);
+ } else {
+ localStorage.removeItem('chat_link');
+ }
+ if (data.chat_link2) {
+ localStorage.setItem('chat_link2', data.chat_link2);
+ } else {
+ localStorage.removeItem('chat_link2');
+ }
+ } else {
+ showError('无法正常连接至服务器!');
+ }
+ };
- return (
- <>
-
-
-
{
- setIsCollapsed(collapsed);
- }}
- selectedKeys={selectedKeys}
- renderWrapper={({itemElement, isSubNav, isInSubNav, props}) => {
- const routerMap = {
- home: "/",
- channel: "/channel",
- token: "/token",
- redemption: "/redemption",
- topup: "/topup",
- user: "/user",
- log: "/log",
- midjourney: "/midjourney",
- setting: "/setting",
- about: "/about",
- chat: "/chat",
- detail: "/detail",
- };
- return (
-
- {itemElement}
-
- );
- }}
- items={headerButtons}
- onSelect={key => {
- setSelectedKeys([key.itemKey]);
- }}
- header={{
- logo:
,
- text: systemName,
- }}
- // footer={{
- // text: '© 2021 NekoAPI',
- // }}
- >
+ useEffect(() => {
+ loadStatus().then(() => {
+ setIsCollapsed(isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true');
+ });
+ let localKey = window.location.pathname.split('/')[1]
+ if (localKey === '') {
+ localKey = 'home'
+ }
+ setSelectedKeys([localKey]);
+ }, []);
-
-
-
-
-
- >
- );
+ return (
+ <>
+
+
+
{
+ setIsCollapsed(collapsed);
+ }}
+ selectedKeys={selectedKeys}
+ renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
+ return (
+
+ {itemElement}
+
+ );
+ }}
+ items={headerButtons}
+ onSelect={key => {
+ setSelectedKeys([key.itemKey]);
+ }}
+ header={{
+ logo:
,
+ text: systemName
+ }}
+ // footer={{
+ // text: '© 2021 NekoAPI',
+ // }}
+ >
+
+
+
+
+
+
+ >
+ );
};
export default SiderBar;
diff --git a/web/src/components/SystemSetting.js b/web/src/components/SystemSetting.js
index 9ed76e0..db85cc6 100644
--- a/web/src/components/SystemSetting.js
+++ b/web/src/components/SystemSetting.js
@@ -1,745 +1,745 @@
import React, { useEffect, useState } from 'react';
-import { Button, Divider, Form, Grid, Header, Modal, Message } from 'semantic-ui-react';
+import { Button, Divider, Form, Grid, Header, Message, Modal } from 'semantic-ui-react';
import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers';
const SystemSetting = () => {
- let [inputs, setInputs] = useState({
- PasswordLoginEnabled: '',
- PasswordRegisterEnabled: '',
- EmailVerificationEnabled: '',
- GitHubOAuthEnabled: '',
- GitHubClientId: '',
- GitHubClientSecret: '',
- LinuxDoOAuthEnabled: '',
- LinuxDoClientId: '',
- LinuxDoClientSecret: '',
- Notice: '',
- SMTPServer: '',
- SMTPPort: '',
- SMTPAccount: '',
- SMTPFrom: '',
- SMTPToken: '',
- ServerAddress: '',
- EpayId: '',
- EpayKey: '',
- Price: 7.3,
- MinTopUp: 1,
- TopupGroupRatio: '',
- PayAddress: '',
- CustomCallbackAddress: '',
- Footer: '',
- WeChatAuthEnabled: '',
- WeChatServerAddress: '',
- WeChatServerToken: '',
- WeChatAccountQRCodeImageURL: '',
- TurnstileCheckEnabled: '',
- TurnstileSiteKey: '',
- TurnstileSecretKey: '',
- RegisterEnabled: '',
- EmailDomainRestrictionEnabled: '',
- EmailDomainWhitelist: '',
- // telegram login
- TelegramOAuthEnabled: '',
- TelegramBotToken: '',
- TelegramBotName: '',
- });
- const [originInputs, setOriginInputs] = useState({});
- let [loading, setLoading] = useState(false);
- const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]);
- const [restrictedDomainInput, setRestrictedDomainInput] = useState('');
- const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false);
+ let [inputs, setInputs] = useState({
+ PasswordLoginEnabled: '',
+ PasswordRegisterEnabled: '',
+ EmailVerificationEnabled: '',
+ GitHubOAuthEnabled: '',
+ GitHubClientId: '',
+ GitHubClientSecret: '',
+ LinuxDoOAuthEnabled: '',
+ LinuxDoClientId: '',
+ LinuxDoClientSecret: '',
+ Notice: '',
+ SMTPServer: '',
+ SMTPPort: '',
+ SMTPAccount: '',
+ SMTPFrom: '',
+ SMTPToken: '',
+ ServerAddress: '',
+ EpayId: '',
+ EpayKey: '',
+ Price: 7.3,
+ MinTopUp: 1,
+ TopupGroupRatio: '',
+ PayAddress: '',
+ CustomCallbackAddress: '',
+ Footer: '',
+ WeChatAuthEnabled: '',
+ WeChatServerAddress: '',
+ WeChatServerToken: '',
+ WeChatAccountQRCodeImageURL: '',
+ TurnstileCheckEnabled: '',
+ TurnstileSiteKey: '',
+ TurnstileSecretKey: '',
+ RegisterEnabled: '',
+ EmailDomainRestrictionEnabled: '',
+ EmailDomainWhitelist: '',
+ // telegram login
+ TelegramOAuthEnabled: '',
+ TelegramBotToken: '',
+ TelegramBotName: ''
+ });
+ const [originInputs, setOriginInputs] = useState({});
+ let [loading, setLoading] = useState(false);
+ const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]);
+ const [restrictedDomainInput, setRestrictedDomainInput] = useState('');
+ const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false);
- const getOptions = async () => {
- const res = await API.get('/api/option/');
- const { success, message, data } = res.data;
- if (success) {
- let newInputs = {};
- data.forEach((item) => {
- if (item.key === 'TopupGroupRatio') {
- item.value = JSON.stringify(JSON.parse(item.value), null, 2);
- }
- newInputs[item.key] = item.value;
- });
- setInputs({
- ...newInputs,
- EmailDomainWhitelist: newInputs.EmailDomainWhitelist.split(',')
- });
- setOriginInputs(newInputs);
+ const getOptions = async () => {
+ const res = await API.get('/api/option/');
+ const { success, message, data } = res.data;
+ if (success) {
+ let newInputs = {};
+ data.forEach((item) => {
+ if (item.key === 'TopupGroupRatio') {
+ item.value = JSON.stringify(JSON.parse(item.value), null, 2);
+ }
+ newInputs[item.key] = item.value;
+ });
+ setInputs({
+ ...newInputs,
+ EmailDomainWhitelist: newInputs.EmailDomainWhitelist.split(',')
+ });
+ setOriginInputs(newInputs);
- setEmailDomainWhitelist(newInputs.EmailDomainWhitelist.split(',').map((item) => {
- return { key: item, text: item, value: item };
- }));
- } else {
- showError(message);
- }
- };
-
- useEffect(() => {
- getOptions().then();
- }, []);
-
- const updateOption = async (key, value) => {
- setLoading(true);
- switch (key) {
- case 'PasswordLoginEnabled':
- case 'PasswordRegisterEnabled':
- case 'EmailVerificationEnabled':
- case 'GitHubOAuthEnabled':
- case 'LinuxDoOAuthEnabled':
- case 'WeChatAuthEnabled':
- case 'TelegramOAuthEnabled':
- case 'TurnstileCheckEnabled':
- case 'EmailDomainRestrictionEnabled':
- case 'RegisterEnabled':
- value = inputs[key] === 'true' ? 'false' : 'true';
- break;
- default:
- break;
- }
- const res = await API.put('/api/option/', {
- key,
- value
- });
- const { success, message } = res.data;
- if (success) {
- if (key === 'EmailDomainWhitelist') {
- value = value.split(',');
- }
- if (key === 'Price') {
- value = parseFloat(value);
- }
- setInputs((inputs) => ({
- ...inputs, [key]: value
- }));
- } else {
- showError(message);
- }
- setLoading(false);
- };
-
- const handleInputChange = async (e, { name, value }) => {
- if (name === 'PasswordLoginEnabled' && inputs[name] === 'true') {
- // block disabling password login
- setShowPasswordWarningModal(true);
- return;
- }
- if (
- name === 'Notice' ||
- name.startsWith('SMTP') ||
- name === 'ServerAddress' ||
- name === 'EpayId' ||
- name === 'EpayKey' ||
- name === 'Price' ||
- name === 'PayAddress' ||
- name === 'GitHubClientId' ||
- name === 'GitHubClientSecret' ||
- name === 'LinuxDoClientId' ||
- name === 'LinuxDoClientSecret' ||
- name === 'WeChatServerAddress' ||
- name === 'WeChatServerToken' ||
- name === 'WeChatAccountQRCodeImageURL' ||
- name === 'TurnstileSiteKey' ||
- name === 'TurnstileSecretKey' ||
- name === 'EmailDomainWhitelist' ||
- name === 'TopupGroupRatio' ||
- name === 'TelegramBotToken' ||
- name === 'TelegramBotName'
- ) {
- setInputs((inputs) => ({ ...inputs, [name]: value }));
- } else {
- await updateOption(name, value);
- }
- };
-
- const submitServerAddress = async () => {
- let ServerAddress = removeTrailingSlash(inputs.ServerAddress);
- await updateOption('ServerAddress', ServerAddress);
- };
-
- const submitPayAddress = async () => {
- if (inputs.ServerAddress === '') {
- showError('请先填写服务器地址');
- return
- }
- if (originInputs['TopupGroupRatio'] !== inputs.TopupGroupRatio) {
- if (!verifyJSON(inputs.TopupGroupRatio)) {
- showError('充值分组倍率不是合法的 JSON 字符串');
- return;
- }
- await updateOption('TopupGroupRatio', inputs.TopupGroupRatio);
- }
- let PayAddress = removeTrailingSlash(inputs.PayAddress);
- await updateOption('PayAddress', PayAddress);
- if (inputs.EpayId !== '') {
- await updateOption('EpayId', inputs.EpayId);
- }
- if (inputs.EpayKey !== '') {
- await updateOption('EpayKey', inputs.EpayKey);
- }
- await updateOption('Price', "" + inputs.Price);
- };
-
- const submitSMTP = async () => {
- if (originInputs['SMTPServer'] !== inputs.SMTPServer) {
- await updateOption('SMTPServer', inputs.SMTPServer);
- }
- if (originInputs['SMTPAccount'] !== inputs.SMTPAccount) {
- await updateOption('SMTPAccount', inputs.SMTPAccount);
- }
- if (originInputs['SMTPFrom'] !== inputs.SMTPFrom) {
- await updateOption('SMTPFrom', inputs.SMTPFrom);
- }
- if (
- originInputs['SMTPPort'] !== inputs.SMTPPort &&
- inputs.SMTPPort !== ''
- ) {
- await updateOption('SMTPPort', inputs.SMTPPort);
- }
- if (
- originInputs['SMTPToken'] !== inputs.SMTPToken &&
- inputs.SMTPToken !== ''
- ) {
- await updateOption('SMTPToken', inputs.SMTPToken);
- }
- };
-
-
- const submitEmailDomainWhitelist = async () => {
- if (
- originInputs['EmailDomainWhitelist'] !== inputs.EmailDomainWhitelist.join(',') &&
- inputs.SMTPToken !== ''
- ) {
- await updateOption('EmailDomainWhitelist', inputs.EmailDomainWhitelist.join(','));
- }
- };
-
- const submitWeChat = async () => {
- if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) {
- await updateOption(
- 'WeChatServerAddress',
- removeTrailingSlash(inputs.WeChatServerAddress)
- );
- }
- if (
- originInputs['WeChatAccountQRCodeImageURL'] !==
- inputs.WeChatAccountQRCodeImageURL
- ) {
- await updateOption(
- 'WeChatAccountQRCodeImageURL',
- inputs.WeChatAccountQRCodeImageURL
- );
- }
- if (
- originInputs['WeChatServerToken'] !== inputs.WeChatServerToken &&
- inputs.WeChatServerToken !== ''
- ) {
- await updateOption('WeChatServerToken', inputs.WeChatServerToken);
- }
- };
-
- const submitGitHubOAuth = async () => {
- if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) {
- await updateOption('GitHubClientId', inputs.GitHubClientId);
- }
- if (
- originInputs['GitHubClientSecret'] !== inputs.GitHubClientSecret &&
- inputs.GitHubClientSecret !== ''
- ) {
- await updateOption('GitHubClientSecret', inputs.GitHubClientSecret);
- }
- };
-
- const submitLinuxDoOAuth = async () => {
- if (originInputs['LinuxDoClientId'] !== inputs.LinuxDoClientId) {
- await updateOption('LinuxDoClientId', inputs.LinuxDoClientId);
- }
- if (
- originInputs['LinuxDoClientSecret'] !== inputs.LinuxDoClientSecret &&
- inputs.LinuxDoClientSecret !== ''
- ) {
- await updateOption('LinuxDoClientSecret', inputs.LinuxDoClientSecret);
- }
- };
-
- const submitTelegramSettings = async () => {
- // await updateOption('TelegramOAuthEnabled', inputs.TelegramOAuthEnabled);
- await updateOption('TelegramBotToken', inputs.TelegramBotToken);
- await updateOption('TelegramBotName', inputs.TelegramBotName);
- };
-
- const submitTurnstile = async () => {
- if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) {
- await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey);
- }
- if (
- originInputs['TurnstileSecretKey'] !== inputs.TurnstileSecretKey &&
- inputs.TurnstileSecretKey !== ''
- ) {
- await updateOption('TurnstileSecretKey', inputs.TurnstileSecretKey);
- }
- };
-
- const submitNewRestrictedDomain = () => {
- const localDomainList = inputs.EmailDomainWhitelist;
- if (restrictedDomainInput !== '' && !localDomainList.includes(restrictedDomainInput)) {
- setRestrictedDomainInput('');
- setInputs({
- ...inputs,
- EmailDomainWhitelist: [...localDomainList, restrictedDomainInput],
- });
- setEmailDomainWhitelist([...EmailDomainWhitelist, {
- key: restrictedDomainInput,
- text: restrictedDomainInput,
- value: restrictedDomainInput,
- }]);
- }
+ setEmailDomainWhitelist(newInputs.EmailDomainWhitelist.split(',').map((item) => {
+ return { key: item, text: item, value: item };
+ }));
+ } else {
+ showError(message);
}
+ };
- return (
-
-
-
-
-
-
-
-
- 更新服务器地址
-
-
- 支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
-
-
-
-
+ useEffect(() => {
+ getOptions().then();
+ }, []);
-
-
-
-
-
-
-
-
-
-
- 更新支付设置
-
-
-
-
-
- {
- showPasswordWarningModal &&
- setShowPasswordWarningModal(false)}
- size={'tiny'}
- style={{ maxWidth: '450px' }}
- >
- 警告
-
- 取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?
-
-
-
-
-
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
- 配置邮箱域名白名单
- 用以防止恶意用户利用临时邮箱批量注册
-
-
-
-
-
-
- {
- submitNewRestrictedDomain();
- }}>填入
- }
- onKeyDown={(e) => {
- if (e.key === 'Enter') {
- submitNewRestrictedDomain();
- }
- }}
- autoComplete='new-password'
- placeholder='输入新的允许的邮箱域名'
- value={restrictedDomainInput}
- onChange={(e, { value }) => {
- setRestrictedDomainInput(value);
- }}
- />
-
- 保存邮箱域名白名单设置
-
-
- 配置 SMTP
- 用以支持系统的邮件发送
-
-
-
-
-
-
-
-
-
-
- 保存 SMTP 设置
-
-
- 配置 GitHub OAuth App
-
- 用以支持通过 GitHub 进行登录注册,
-
- 点击此处
-
- 管理你的 GitHub OAuth App
-
-
-
- Homepage URL 填 {inputs.ServerAddress}
- ,Authorization callback URL 填{' '}
- {`${inputs.ServerAddress}/oauth/github`}
-
-
-
-
-
-
- 保存 GitHub OAuth 设置
-
-
-
- 配置 LINUX DO Oauth
-
- 用以支持通过 LINUX DO 进行登录注册,
-
- 点击此处
-
- 管理你的 LINUX DO OAuth
-
-
-
- Homepage URL 填 {inputs.ServerAddress}
- ,Authorization callback URL 填{' '}
- {`${inputs.ServerAddress}/oauth/linuxdo`}
-
-
-
-
-
-
- 保存 LINUX DO OAuth 设置
-
-
-
- 配置 WeChat Server
-
- 用以支持通过微信进行登录注册,
-
- 点击此处
-
- 了解 WeChat Server
-
-
-
-
-
-
-
-
- 保存 WeChat Server 设置
-
-
-
-
-
-
-
-
- 保存 Telegram 登录设置
-
-
-
- 配置 Turnstile
-
- 用以支持用户校验,
-
- 点击此处
-
- 管理你的 Turnstile Sites,推荐选择 Invisible Widget Type
-
-
-
-
-
-
-
- 保存 Turnstile 设置
-
-
-
-
- );
+ const updateOption = async (key, value) => {
+ setLoading(true);
+ switch (key) {
+ case 'PasswordLoginEnabled':
+ case 'PasswordRegisterEnabled':
+ case 'EmailVerificationEnabled':
+ case 'GitHubOAuthEnabled':
+ case 'LinuxDoOAuthEnabled':
+ case 'WeChatAuthEnabled':
+ case 'TelegramOAuthEnabled':
+ case 'TurnstileCheckEnabled':
+ case 'EmailDomainRestrictionEnabled':
+ case 'RegisterEnabled':
+ value = inputs[key] === 'true' ? 'false' : 'true';
+ break;
+ default:
+ break;
+ }
+ const res = await API.put('/api/option/', {
+ key,
+ value
+ });
+ const { success, message } = res.data;
+ if (success) {
+ if (key === 'EmailDomainWhitelist') {
+ value = value.split(',');
+ }
+ if (key === 'Price') {
+ value = parseFloat(value);
+ }
+ setInputs((inputs) => ({
+ ...inputs, [key]: value
+ }));
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
+
+ const handleInputChange = async (e, { name, value }) => {
+ if (name === 'PasswordLoginEnabled' && inputs[name] === 'true') {
+ // block disabling password login
+ setShowPasswordWarningModal(true);
+ return;
+ }
+ if (
+ name === 'Notice' ||
+ name.startsWith('SMTP') ||
+ name === 'ServerAddress' ||
+ name === 'EpayId' ||
+ name === 'EpayKey' ||
+ name === 'Price' ||
+ name === 'PayAddress' ||
+ name === 'GitHubClientId' ||
+ name === 'GitHubClientSecret' ||
+ name === 'LinuxDoClientId' ||
+ name === 'LinuxDoClientSecret' ||
+ name === 'WeChatServerAddress' ||
+ name === 'WeChatServerToken' ||
+ name === 'WeChatAccountQRCodeImageURL' ||
+ name === 'TurnstileSiteKey' ||
+ name === 'TurnstileSecretKey' ||
+ name === 'EmailDomainWhitelist' ||
+ name === 'TopupGroupRatio' ||
+ name === 'TelegramBotToken' ||
+ name === 'TelegramBotName'
+ ) {
+ setInputs((inputs) => ({ ...inputs, [name]: value }));
+ } else {
+ await updateOption(name, value);
+ }
+ };
+
+ const submitServerAddress = async () => {
+ let ServerAddress = removeTrailingSlash(inputs.ServerAddress);
+ await updateOption('ServerAddress', ServerAddress);
+ };
+
+ const submitPayAddress = async () => {
+ if (inputs.ServerAddress === '') {
+ showError('请先填写服务器地址');
+ return;
+ }
+ if (originInputs['TopupGroupRatio'] !== inputs.TopupGroupRatio) {
+ if (!verifyJSON(inputs.TopupGroupRatio)) {
+ showError('充值分组倍率不是合法的 JSON 字符串');
+ return;
+ }
+ await updateOption('TopupGroupRatio', inputs.TopupGroupRatio);
+ }
+ let PayAddress = removeTrailingSlash(inputs.PayAddress);
+ await updateOption('PayAddress', PayAddress);
+ if (inputs.EpayId !== '') {
+ await updateOption('EpayId', inputs.EpayId);
+ }
+ if (inputs.EpayKey !== '') {
+ await updateOption('EpayKey', inputs.EpayKey);
+ }
+ await updateOption('Price', '' + inputs.Price);
+ };
+
+ const submitSMTP = async () => {
+ if (originInputs['SMTPServer'] !== inputs.SMTPServer) {
+ await updateOption('SMTPServer', inputs.SMTPServer);
+ }
+ if (originInputs['SMTPAccount'] !== inputs.SMTPAccount) {
+ await updateOption('SMTPAccount', inputs.SMTPAccount);
+ }
+ if (originInputs['SMTPFrom'] !== inputs.SMTPFrom) {
+ await updateOption('SMTPFrom', inputs.SMTPFrom);
+ }
+ if (
+ originInputs['SMTPPort'] !== inputs.SMTPPort &&
+ inputs.SMTPPort !== ''
+ ) {
+ await updateOption('SMTPPort', inputs.SMTPPort);
+ }
+ if (
+ originInputs['SMTPToken'] !== inputs.SMTPToken &&
+ inputs.SMTPToken !== ''
+ ) {
+ await updateOption('SMTPToken', inputs.SMTPToken);
+ }
+ };
+
+
+ const submitEmailDomainWhitelist = async () => {
+ if (
+ originInputs['EmailDomainWhitelist'] !== inputs.EmailDomainWhitelist.join(',') &&
+ inputs.SMTPToken !== ''
+ ) {
+ await updateOption('EmailDomainWhitelist', inputs.EmailDomainWhitelist.join(','));
+ }
+ };
+
+ const submitWeChat = async () => {
+ if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) {
+ await updateOption(
+ 'WeChatServerAddress',
+ removeTrailingSlash(inputs.WeChatServerAddress)
+ );
+ }
+ if (
+ originInputs['WeChatAccountQRCodeImageURL'] !==
+ inputs.WeChatAccountQRCodeImageURL
+ ) {
+ await updateOption(
+ 'WeChatAccountQRCodeImageURL',
+ inputs.WeChatAccountQRCodeImageURL
+ );
+ }
+ if (
+ originInputs['WeChatServerToken'] !== inputs.WeChatServerToken &&
+ inputs.WeChatServerToken !== ''
+ ) {
+ await updateOption('WeChatServerToken', inputs.WeChatServerToken);
+ }
+ };
+
+ const submitGitHubOAuth = async () => {
+ if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) {
+ await updateOption('GitHubClientId', inputs.GitHubClientId);
+ }
+ if (
+ originInputs['GitHubClientSecret'] !== inputs.GitHubClientSecret &&
+ inputs.GitHubClientSecret !== ''
+ ) {
+ await updateOption('GitHubClientSecret', inputs.GitHubClientSecret);
+ }
+ };
+
+ const submitLinuxDoOAuth = async () => {
+ if (originInputs['LinuxDoClientId'] !== inputs.LinuxDoClientId) {
+ await updateOption('LinuxDoClientId', inputs.LinuxDoClientId);
+ }
+ if (
+ originInputs['LinuxDoClientSecret'] !== inputs.LinuxDoClientSecret &&
+ inputs.LinuxDoClientSecret !== ''
+ ) {
+ await updateOption('LinuxDoClientSecret', inputs.LinuxDoClientSecret);
+ }
+ };
+
+ const submitTelegramSettings = async () => {
+ // await updateOption('TelegramOAuthEnabled', inputs.TelegramOAuthEnabled);
+ await updateOption('TelegramBotToken', inputs.TelegramBotToken);
+ await updateOption('TelegramBotName', inputs.TelegramBotName);
+ };
+
+ const submitTurnstile = async () => {
+ if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) {
+ await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey);
+ }
+ if (
+ originInputs['TurnstileSecretKey'] !== inputs.TurnstileSecretKey &&
+ inputs.TurnstileSecretKey !== ''
+ ) {
+ await updateOption('TurnstileSecretKey', inputs.TurnstileSecretKey);
+ }
+ };
+
+ const submitNewRestrictedDomain = () => {
+ const localDomainList = inputs.EmailDomainWhitelist;
+ if (restrictedDomainInput !== '' && !localDomainList.includes(restrictedDomainInput)) {
+ setRestrictedDomainInput('');
+ setInputs({
+ ...inputs,
+ EmailDomainWhitelist: [...localDomainList, restrictedDomainInput]
+ });
+ setEmailDomainWhitelist([...EmailDomainWhitelist, {
+ key: restrictedDomainInput,
+ text: restrictedDomainInput,
+ value: restrictedDomainInput
+ }]);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ 更新服务器地址
+
+
+ 支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 更新支付设置
+
+
+
+
+
+ {
+ showPasswordWarningModal &&
+ setShowPasswordWarningModal(false)}
+ size={'tiny'}
+ style={{ maxWidth: '450px' }}
+ >
+ 警告
+
+ 取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 配置邮箱域名白名单
+ 用以防止恶意用户利用临时邮箱批量注册
+
+
+
+
+
+
+ {
+ submitNewRestrictedDomain();
+ }}>填入
+ }
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ submitNewRestrictedDomain();
+ }
+ }}
+ autoComplete="new-password"
+ placeholder="输入新的允许的邮箱域名"
+ value={restrictedDomainInput}
+ onChange={(e, { value }) => {
+ setRestrictedDomainInput(value);
+ }}
+ />
+
+ 保存邮箱域名白名单设置
+
+
+ 配置 SMTP
+ 用以支持系统的邮件发送
+
+
+
+
+
+
+
+
+
+
+ 保存 SMTP 设置
+
+
+ 配置 GitHub OAuth App
+
+ 用以支持通过 GitHub 进行登录注册,
+
+ 点击此处
+
+ 管理你的 GitHub OAuth App
+
+
+
+ Homepage URL 填 {inputs.ServerAddress}
+ ,Authorization callback URL 填{' '}
+ {`${inputs.ServerAddress}/oauth/github`}
+
+
+
+
+
+
+ 保存 GitHub OAuth 设置
+
+
+
+ 配置 LINUX DO Oauth
+
+ 用以支持通过 LINUX DO 进行登录注册,
+
+ 点击此处
+
+ 管理你的 LINUX DO OAuth
+
+
+
+ Homepage URL 填 {inputs.ServerAddress}
+ ,Authorization callback URL 填{' '}
+ {`${inputs.ServerAddress}/oauth/linuxdo`}
+
+
+
+
+
+
+ 保存 LINUX DO OAuth 设置
+
+
+
+ 配置 WeChat Server
+
+ 用以支持通过微信进行登录注册,
+
+ 点击此处
+
+ 了解 WeChat Server
+
+
+
+
+
+
+
+
+ 保存 WeChat Server 设置
+
+
+
+
+
+
+
+
+ 保存 Telegram 登录设置
+
+
+
+ 配置 Turnstile
+
+ 用以支持用户校验,
+
+ 点击此处
+
+ 管理你的 Turnstile Sites,推荐选择 Invisible Widget Type
+
+
+
+
+
+
+
+ 保存 Turnstile 设置
+
+
+
+
+ );
};
export default SystemSetting;
diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js
index fdd1329..5901bfd 100644
--- a/web/src/components/TokensTable.js
+++ b/web/src/components/TokensTable.js
@@ -1,562 +1,586 @@
-import React, {useEffect, useState} from 'react';
-import {API, copy, isAdmin, showError, showSuccess, showWarning, timestamp2string} from '../helpers';
+import React, { useEffect, useState } from 'react';
+import { API, copy, showError, showSuccess, timestamp2string } from '../helpers';
-import {ITEMS_PER_PAGE} from '../constants';
-import {renderQuota, stringToColor} from '../helpers/render';
-import {
- Avatar,
- Tag,
- Table,
- Button,
- Popover,
- Form,
- Modal,
- Popconfirm,
- SplitButtonGroup,
- Dropdown
-} from "@douyinfe/semi-ui";
+import { ITEMS_PER_PAGE } from '../constants';
+import { renderQuota } from '../helpers/render';
+import { Button, Dropdown, Form, Modal, Popconfirm, Popover, SplitButtonGroup, Table, Tag } from '@douyinfe/semi-ui';
+
+import { IconTreeTriangleDown } from '@douyinfe/semi-icons';
+import EditToken from '../pages/Token/EditToken';
-import {
- IconTreeTriangleDown,
-} from '@douyinfe/semi-icons';
-import EditToken from "../pages/Token/EditToken";
const COPY_OPTIONS = [
- {key: 'next', text: 'ChatGPT Next Web', value: 'next'},
- {key: 'ama', text: 'ChatGPT Web & Midjourney', value: 'ama'},
- {key: 'opencat', text: 'OpenCat', value: 'opencat'},
+ { key: 'next', text: 'ChatGPT Next Web', value: 'next' },
+ { key: 'ama', text: 'ChatGPT Web & Midjourney', value: 'ama' },
+ { key: 'opencat', text: 'OpenCat', value: 'opencat' }
];
const OPEN_LINK_OPTIONS = [
- {key: 'ama', text: 'ChatGPT Web & Midjourney', value: 'ama'},
- {key: 'opencat', text: 'OpenCat', value: 'opencat'},
+ { key: 'ama', text: 'ChatGPT Web & Midjourney', value: 'ama' },
+ { key: 'opencat', text: 'OpenCat', value: 'opencat' }
];
function renderTimestamp(timestamp) {
- return (
- <>
- {timestamp2string(timestamp)}
- >
- );
+ return (
+ <>
+ {timestamp2string(timestamp)}
+ >
+ );
}
function renderStatus(status, model_limits_enabled = false) {
- switch (status) {
- case 1:
- if (model_limits_enabled) {
- return 已启用:限制模型;
- } else {
- return 已启用;
- }
- case 2:
- return 已禁用 ;
- case 3:
- return 已过期 ;
- case 4:
- return 已耗尽 ;
- default:
- return 未知状态 ;
- }
+ switch (status) {
+ case 1:
+ if (model_limits_enabled) {
+ return 已启用:限制模型;
+ } else {
+ return 已启用;
+ }
+ case 2:
+ return 已禁用 ;
+ case 3:
+ return 已过期 ;
+ case 4:
+ return 已耗尽 ;
+ default:
+ return 未知状态 ;
+ }
}
const TokensTable = () => {
- const link_menu = [
- {node: 'item', key: 'next', name: 'ChatGPT Next Web', onClick: () => {onOpenLink('next')}},
- {node: 'item', key: 'ama', name: 'AMA 问天', value: 'ama'},
- {node: 'item', key: 'next-mj', name: 'ChatGPT Web & Midjourney', value: 'next-mj', onClick: () => {onOpenLink('next-mj')}},
- {node: 'item', key: 'opencat', name: 'OpenCat', value: 'opencat'},
- ];
+ const link_menu = [
+ {
+ node: 'item', key: 'next', name: 'ChatGPT Next Web', onClick: () => {
+ onOpenLink('next');
+ }
+ },
+ { node: 'item', key: 'ama', name: 'AMA 问天', value: 'ama' },
+ {
+ node: 'item', key: 'next-mj', name: 'ChatGPT Web & Midjourney', value: 'next-mj', onClick: () => {
+ onOpenLink('next-mj');
+ }
+ },
+ { node: 'item', key: 'opencat', name: 'OpenCat', value: 'opencat' }
+ ];
- const columns = [
- {
- title: '名称',
- dataIndex: 'name',
- },
- {
- title: '状态',
- dataIndex: 'status',
- key: 'status',
- render: (text, record, index) => {
- return (
-
- {renderStatus(text, record.model_limits_enabled)}
-
- );
- },
- },
- {
- title: '已用额度',
- dataIndex: 'used_quota',
- render: (text, record, index) => {
- return (
-
- {renderQuota(parseInt(text))}
-
- );
- },
- },
- {
- title: '剩余额度',
- dataIndex: 'remain_quota',
- render: (text, record, index) => {
- return (
-
- {record.unlimited_quota ? 无限制 : {renderQuota(parseInt(text))}}
-
- );
- },
- },
- {
- title: '创建时间',
- dataIndex: 'created_time',
- render: (text, record, index) => {
- return (
-
- {renderTimestamp(text)}
-
- );
- },
- },
- {
- title: '过期时间',
- dataIndex: 'expired_time',
- render: (text, record, index) => {
- return (
-
- {record.expired_time === -1 ? "永不过期" : renderTimestamp(text)}
-
- );
- },
- },
- {
- title: '',
- dataIndex: 'operate',
- render: (text, record, index) => (
-
-
-
-
-
-
-
- {onOpenLink('next', record.key)}},
- {node: 'item', key: 'next-mj', disabled: !localStorage.getItem('chat_link2'), name: 'ChatGPT Web & Midjourney', onClick: () => {onOpenLink('next-mj', record.key)}},
- {node: 'item', key: 'ama', name: 'AMA 问天(BotGem)', onClick: () => {onOpenLink('ama', record.key)}},
- {node: 'item', key: 'opencat', name: 'OpenCat', onClick: () => {onOpenLink('opencat', record.key)}},
- ]
- }
- >
- }>
-
-
-
{
- manageToken(record.id, 'delete', record).then(
- () => {
- removeRecord(record.key);
- }
- )
- }}
- >
-
-
- {
- record.status === 1 ?
-
:
-
- }
-
-
- ),
- },
- ];
-
- const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
- const [showEdit, setShowEdit] = useState(false);
- const [tokens, setTokens] = useState([]);
- const [selectedKeys, setSelectedKeys] = useState([]);
- const [tokenCount, setTokenCount] = useState(pageSize);
- const [loading, setLoading] = useState(true);
- const [activePage, setActivePage] = useState(1);
- const [searchKeyword, setSearchKeyword] = useState('');
- const [searchToken, setSearchToken] = useState('');
- const [searching, setSearching] = useState(false);
- const [showTopUpModal, setShowTopUpModal] = useState(false);
- const [targetTokenIdx, setTargetTokenIdx] = useState(0);
- const [editingToken, setEditingToken] = useState({
- id: undefined,
- });
-
- const closeEdit = () => {
- setShowEdit(false);
- setTimeout(() => {
- setEditingToken({
- id: undefined,
- });
- }, 500);
- }
-
- const setTokensFormat = (tokens) => {
- setTokens(tokens);
- if (tokens.length >= pageSize) {
- setTokenCount(tokens.length + pageSize);
- } else {
- setTokenCount(tokens.length);
- }
- }
-
- let pageData = tokens.slice((activePage - 1) * pageSize, activePage * pageSize);
- const loadTokens = async (startIdx) => {
- setLoading(true);
- const res = await API.get(`/api/token/?p=${startIdx}&size=${pageSize}`);
- const {success, message, data} = res.data;
- if (success) {
- if (startIdx === 0) {
- setTokensFormat(data);
- } else {
- let newTokens = [...tokens];
- newTokens.splice(startIdx * pageSize, data.length, ...data);
- setTokensFormat(newTokens);
+ const columns = [
+ {
+ title: '名称',
+ dataIndex: 'name'
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ key: 'status',
+ render: (text, record, index) => {
+ return (
+
+ {renderStatus(text, record.model_limits_enabled)}
+
+ );
+ }
+ },
+ {
+ title: '已用额度',
+ dataIndex: 'used_quota',
+ render: (text, record, index) => {
+ return (
+
+ {renderQuota(parseInt(text))}
+
+ );
+ }
+ },
+ {
+ title: '剩余额度',
+ dataIndex: 'remain_quota',
+ render: (text, record, index) => {
+ return (
+
+ {record.unlimited_quota ? 无限制 :
+ {renderQuota(parseInt(text))}}
+
+ );
+ }
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'created_time',
+ render: (text, record, index) => {
+ return (
+
+ {renderTimestamp(text)}
+
+ );
+ }
+ },
+ {
+ title: '过期时间',
+ dataIndex: 'expired_time',
+ render: (text, record, index) => {
+ return (
+
+ {record.expired_time === -1 ? '永不过期' : renderTimestamp(text)}
+
+ );
+ }
+ },
+ {
+ title: '',
+ dataIndex: 'operate',
+ render: (text, record, index) => (
+
+
{
- (async () => {
- if (activePage === Math.ceil(tokens.length / pageSize) + 1) {
- // In this case we have to load more data and then append them.
- await loadTokens(activePage - 1);
- }
- setActivePage(activePage);
- })();
- };
-
- const refresh = async () => {
- await loadTokens(activePage - 1);
- };
-
- const onCopy = async (type, key) => {
- let status = localStorage.getItem('status');
- let serverAddress = '';
- if (status) {
- status = JSON.parse(status);
- serverAddress = status.server_address;
- }
- if (serverAddress === '') {
- serverAddress = window.location.origin;
- }
- let encodedServerAddress = encodeURIComponent(serverAddress);
- const nextLink = localStorage.getItem('chat_link');
- const mjLink = localStorage.getItem('chat_link2');
- let nextUrl;
-
- if (nextLink) {
- nextUrl = nextLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
- } else {
- nextUrl = `https://chat.oneapi.pro/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
- }
-
- let url;
- switch (type) {
- case 'ama':
- url = mjLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
- break;
- case 'opencat':
- url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`;
- break;
- case 'next':
- url = nextUrl;
- break;
- default:
- url = `sk-${key}`;
- }
- // if (await copy(url)) {
- // showSuccess('已复制到剪贴板!');
- // } else {
- // showWarning('无法复制到剪贴板,请手动复制,已将令牌填入搜索框。');
- // setSearchKeyword(url);
- // }
- };
-
- const copyText = async (text) => {
- if (await copy(text)) {
- showSuccess('已复制到剪贴板!');
- } else {
- // setSearchKeyword(text);
- Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
- }
- }
-
- const onOpenLink = async (type, key) => {
- let status = localStorage.getItem('status');
- let serverAddress = '';
- if (status) {
- status = JSON.parse(status);
- serverAddress = status.server_address;
- }
- if (serverAddress === '') {
- serverAddress = window.location.origin;
- }
- let encodedServerAddress = encodeURIComponent(serverAddress);
- const chatLink = localStorage.getItem('chat_link');
- const mjLink = localStorage.getItem('chat_link2');
- let defaultUrl;
-
- if (chatLink) {
- defaultUrl = chatLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
- }
- let url;
- switch (type) {
- case 'ama':
- url = `ama://set-api-key?server=${encodedServerAddress}&key=sk-${key}`;
- break;
- case 'opencat':
- url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`;
- break;
- case 'next-mj':
- url = mjLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
- break;
- default:
- if (!chatLink) {
- showError('管理员未设置聊天链接')
- return;
+ style={{ padding: 20 }}
+ position="top"
+ >
+
+
+
+
+
+ {
+ onOpenLink('next', record.key);
+ }
+ },
+ {
+ node: 'item',
+ key: 'next-mj',
+ disabled: !localStorage.getItem('chat_link2'),
+ name: 'ChatGPT Web & Midjourney',
+ onClick: () => {
+ onOpenLink('next-mj', record.key);
+ }
+ },
+ {
+ node: 'item', key: 'ama', name: 'AMA 问天(BotGem)', onClick: () => {
+ onOpenLink('ama', record.key);
+ }
+ },
+ {
+ node: 'item', key: 'opencat', name: 'OpenCat', onClick: () => {
+ onOpenLink('opencat', record.key);
+ }
}
- url = defaultUrl;
- }
-
- window.open(url, '_blank');
- }
-
- useEffect(() => {
- loadTokens(0)
- .then()
- .catch((reason) => {
- showError(reason);
- });
- }, [pageSize]);
-
- const removeRecord = key => {
- let newDataSource = [...tokens];
- if (key != null) {
- let idx = newDataSource.findIndex(data => data.key === key);
-
- if (idx > -1) {
- newDataSource.splice(idx, 1);
- setTokensFormat(newDataSource);
+ ]
}
- }
- };
-
- const manageToken = async (id, action, record) => {
- setLoading(true);
- let data = {id};
- let res;
- switch (action) {
- case 'delete':
- res = await API.delete(`/api/token/${id}/`);
- break;
- case 'enable':
- data.status = 1;
- res = await API.put('/api/token/?status_only=true', data);
- break;
- case 'disable':
- data.status = 2;
- res = await API.put('/api/token/?status_only=true', data);
- break;
- }
- const {success, message} = res.data;
- if (success) {
- showSuccess('操作成功完成!');
- let token = res.data.data;
- let newTokens = [...tokens];
- // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
- if (action === 'delete') {
-
- } else {
- record.status = token.status;
- // newTokens[realIdx].status = token.status;
- }
- setTokensFormat(newTokens);
- } else {
- showError(message);
- }
- setLoading(false);
- };
-
- const searchTokens = async () => {
- if (searchKeyword === '' && searchToken === '') {
- // if keyword is blank, load files instead.
- await loadTokens(0);
- setActivePage(1);
- return;
- }
- setSearching(true);
- const res = await API.get(`/api/token/search?keyword=${searchKeyword}&token=${searchToken}`);
- const {success, message, data} = res.data;
- if (success) {
- setTokensFormat(data);
- setActivePage(1);
- } else {
- showError(message);
- }
- setSearching(false);
- };
-
- const handleKeywordChange = async (value) => {
- setSearchKeyword(value.trim());
- };
-
- const handleSearchTokenChange = async (value) => {
- setSearchToken(value.trim());
- };
-
- const sortToken = (key) => {
- if (tokens.length === 0) return;
- setLoading(true);
- let sortedTokens = [...tokens];
- sortedTokens.sort((a, b) => {
- return ('' + a[key]).localeCompare(b[key]);
- });
- if (sortedTokens[0].id === tokens[0].id) {
- sortedTokens.reverse();
- }
- setTokens(sortedTokens);
- setLoading(false);
- };
-
-
- const handlePageChange = page => {
- setActivePage(page);
- if (page === Math.ceil(tokens.length / pageSize) + 1) {
- // In this case we have to load more data and then append them.
- loadTokens(page - 1).then(r => {
- });
- }
- };
-
- const rowSelection = {
- onSelect: (record, selected) => {
- },
- onSelectAll: (selected, selectedRows) => {
- },
- onChange: (selectedRowKeys, selectedRows) => {
- setSelectedKeys(selectedRows);
- },
- };
-
- const handleRow = (record, index) => {
- if (record.status !== 1) {
- return {
- style: {
- background: 'var(--semi-color-disabled-border)',
- },
- };
- } else {
- return {};
- }
- };
-
- return (
- <>
-
-
-
-
-
-
-
- `第 ${page.currentStart} - ${page.currentEnd} 条,共 ${tokens.length} 条`,
- onPageSizeChange: (size) => {
- setPageSize(size);
- setActivePage(1);
- },
- onPageChange: handlePageChange,
- }} loading={loading} rowSelection={rowSelection} onRow={handleRow}>
-
-
+
+
+
{
+ manageToken(record.id, 'delete', record).then(
() => {
- setEditingToken({
- id: undefined,
- });
- setShowEdit(true);
+ removeRecord(record.key);
}
- }>添加令牌
-
+ 删除
+
+ {
+ record.status === 1 ?
+
{
- if (selectedKeys.length === 0) {
- showError('请至少选择一个令牌!');
- return;
- }
- let keys = "";
- for (let i = 0; i < selectedKeys.length; i++) {
- keys += selectedKeys[i].name + " sk-" + selectedKeys[i].key + "\n";
- }
- await copyText(keys);
+ manageToken(
+ record.id,
+ 'disable',
+ record
+ );
}
- }>复制所选令牌到剪贴板
- >
- );
+ }>禁用 :
+
{
+ manageToken(
+ record.id,
+ 'enable',
+ record
+ );
+ }
+ }>启用
+ }
+
{
+ setEditingToken(record);
+ setShowEdit(true);
+ }
+ }>编辑
+
+ )
+ }
+ ];
+
+ const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
+ const [showEdit, setShowEdit] = useState(false);
+ const [tokens, setTokens] = useState([]);
+ const [selectedKeys, setSelectedKeys] = useState([]);
+ const [tokenCount, setTokenCount] = useState(pageSize);
+ const [loading, setLoading] = useState(true);
+ const [activePage, setActivePage] = useState(1);
+ const [searchKeyword, setSearchKeyword] = useState('');
+ const [searchToken, setSearchToken] = useState('');
+ const [searching, setSearching] = useState(false);
+ const [showTopUpModal, setShowTopUpModal] = useState(false);
+ const [targetTokenIdx, setTargetTokenIdx] = useState(0);
+ const [editingToken, setEditingToken] = useState({
+ id: undefined
+ });
+
+ const closeEdit = () => {
+ setShowEdit(false);
+ setTimeout(() => {
+ setEditingToken({
+ id: undefined
+ });
+ }, 500);
+ };
+
+ const setTokensFormat = (tokens) => {
+ setTokens(tokens);
+ if (tokens.length >= pageSize) {
+ setTokenCount(tokens.length + pageSize);
+ } else {
+ setTokenCount(tokens.length);
+ }
+ };
+
+ let pageData = tokens.slice((activePage - 1) * pageSize, activePage * pageSize);
+ const loadTokens = async (startIdx) => {
+ setLoading(true);
+ const res = await API.get(`/api/token/?p=${startIdx}&size=${pageSize}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ if (startIdx === 0) {
+ setTokensFormat(data);
+ } else {
+ let newTokens = [...tokens];
+ newTokens.splice(startIdx * pageSize, data.length, ...data);
+ setTokensFormat(newTokens);
+ }
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
+
+ const onPaginationChange = (e, { activePage }) => {
+ (async () => {
+ if (activePage === Math.ceil(tokens.length / pageSize) + 1) {
+ // In this case we have to load more data and then append them.
+ await loadTokens(activePage - 1);
+ }
+ setActivePage(activePage);
+ })();
+ };
+
+ const refresh = async () => {
+ await loadTokens(activePage - 1);
+ };
+
+ const onCopy = async (type, key) => {
+ let status = localStorage.getItem('status');
+ let serverAddress = '';
+ if (status) {
+ status = JSON.parse(status);
+ serverAddress = status.server_address;
+ }
+ if (serverAddress === '') {
+ serverAddress = window.location.origin;
+ }
+ let encodedServerAddress = encodeURIComponent(serverAddress);
+ const nextLink = localStorage.getItem('chat_link');
+ const mjLink = localStorage.getItem('chat_link2');
+ let nextUrl;
+
+ if (nextLink) {
+ nextUrl = nextLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
+ } else {
+ nextUrl = `https://chat.oneapi.pro/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
+ }
+
+ let url;
+ switch (type) {
+ case 'ama':
+ url = mjLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
+ break;
+ case 'opencat':
+ url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`;
+ break;
+ case 'next':
+ url = nextUrl;
+ break;
+ default:
+ url = `sk-${key}`;
+ }
+ // if (await copy(url)) {
+ // showSuccess('已复制到剪贴板!');
+ // } else {
+ // showWarning('无法复制到剪贴板,请手动复制,已将令牌填入搜索框。');
+ // setSearchKeyword(url);
+ // }
+ };
+
+ const copyText = async (text) => {
+ if (await copy(text)) {
+ showSuccess('已复制到剪贴板!');
+ } else {
+ // setSearchKeyword(text);
+ Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
+ }
+ };
+
+ const onOpenLink = async (type, key) => {
+ let status = localStorage.getItem('status');
+ let serverAddress = '';
+ if (status) {
+ status = JSON.parse(status);
+ serverAddress = status.server_address;
+ }
+ if (serverAddress === '') {
+ serverAddress = window.location.origin;
+ }
+ let encodedServerAddress = encodeURIComponent(serverAddress);
+ const chatLink = localStorage.getItem('chat_link');
+ const mjLink = localStorage.getItem('chat_link2');
+ let defaultUrl;
+
+ if (chatLink) {
+ defaultUrl = chatLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
+ }
+ let url;
+ switch (type) {
+ case 'ama':
+ url = `ama://set-api-key?server=${encodedServerAddress}&key=sk-${key}`;
+ break;
+ case 'opencat':
+ url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`;
+ break;
+ case 'next-mj':
+ url = mjLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
+ break;
+ default:
+ if (!chatLink) {
+ showError('管理员未设置聊天链接');
+ return;
+ }
+ url = defaultUrl;
+ }
+
+ window.open(url, '_blank');
+ };
+
+ useEffect(() => {
+ loadTokens(0)
+ .then()
+ .catch((reason) => {
+ showError(reason);
+ });
+ }, [pageSize]);
+
+ const removeRecord = key => {
+ let newDataSource = [...tokens];
+ if (key != null) {
+ let idx = newDataSource.findIndex(data => data.key === key);
+
+ if (idx > -1) {
+ newDataSource.splice(idx, 1);
+ setTokensFormat(newDataSource);
+ }
+ }
+ };
+
+ const manageToken = async (id, action, record) => {
+ setLoading(true);
+ let data = { id };
+ let res;
+ switch (action) {
+ case 'delete':
+ res = await API.delete(`/api/token/${id}/`);
+ break;
+ case 'enable':
+ data.status = 1;
+ res = await API.put('/api/token/?status_only=true', data);
+ break;
+ case 'disable':
+ data.status = 2;
+ res = await API.put('/api/token/?status_only=true', data);
+ break;
+ }
+ const { success, message } = res.data;
+ if (success) {
+ showSuccess('操作成功完成!');
+ let token = res.data.data;
+ let newTokens = [...tokens];
+ // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
+ if (action === 'delete') {
+
+ } else {
+ record.status = token.status;
+ // newTokens[realIdx].status = token.status;
+ }
+ setTokensFormat(newTokens);
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
+
+ const searchTokens = async () => {
+ if (searchKeyword === '' && searchToken === '') {
+ // if keyword is blank, load files instead.
+ await loadTokens(0);
+ setActivePage(1);
+ return;
+ }
+ setSearching(true);
+ const res = await API.get(`/api/token/search?keyword=${searchKeyword}&token=${searchToken}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ setTokensFormat(data);
+ setActivePage(1);
+ } else {
+ showError(message);
+ }
+ setSearching(false);
+ };
+
+ const handleKeywordChange = async (value) => {
+ setSearchKeyword(value.trim());
+ };
+
+ const handleSearchTokenChange = async (value) => {
+ setSearchToken(value.trim());
+ };
+
+ const sortToken = (key) => {
+ if (tokens.length === 0) return;
+ setLoading(true);
+ let sortedTokens = [...tokens];
+ sortedTokens.sort((a, b) => {
+ return ('' + a[key]).localeCompare(b[key]);
+ });
+ if (sortedTokens[0].id === tokens[0].id) {
+ sortedTokens.reverse();
+ }
+ setTokens(sortedTokens);
+ setLoading(false);
+ };
+
+
+ const handlePageChange = page => {
+ setActivePage(page);
+ if (page === Math.ceil(tokens.length / pageSize) + 1) {
+ // In this case we have to load more data and then append them.
+ loadTokens(page - 1).then(r => {
+ });
+ }
+ };
+
+ const rowSelection = {
+ onSelect: (record, selected) => {
+ },
+ onSelectAll: (selected, selectedRows) => {
+ },
+ onChange: (selectedRowKeys, selectedRows) => {
+ setSelectedKeys(selectedRows);
+ }
+ };
+
+ const handleRow = (record, index) => {
+ if (record.status !== 1) {
+ return {
+ style: {
+ background: 'var(--semi-color-disabled-border)'
+ }
+ };
+ } else {
+ return {};
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+ 查询
+
+
+ `第 ${page.currentStart} - ${page.currentEnd} 条,共 ${tokens.length} 条`,
+ onPageSizeChange: (size) => {
+ setPageSize(size);
+ setActivePage(1);
+ },
+ onPageChange: handlePageChange
+ }} loading={loading} rowSelection={rowSelection} onRow={handleRow}>
+
+ {
+ setEditingToken({
+ id: undefined
+ });
+ setShowEdit(true);
+ }
+ }>添加令牌
+ {
+ if (selectedKeys.length === 0) {
+ showError('请至少选择一个令牌!');
+ return;
+ }
+ let keys = '';
+ for (let i = 0; i < selectedKeys.length; i++) {
+ keys += selectedKeys[i].name + ' sk-' + selectedKeys[i].key + '\n';
+ }
+ await copyText(keys);
+ }
+ }>复制所选令牌到剪贴板
+ >
+ );
};
export default TokensTable;
diff --git a/web/src/components/UsersTable.js b/web/src/components/UsersTable.js
index ce8e594..7757afa 100644
--- a/web/src/components/UsersTable.js
+++ b/web/src/components/UsersTable.js
@@ -1,337 +1,338 @@
-import React, {useEffect, useState} from 'react';
-import {API, isAdmin, showError, showSuccess} from '../helpers';
-import {Button, Modal, Popconfirm, Popover, Table, Tag, Form, Tooltip, Space} from "@douyinfe/semi-ui";
-import {ITEMS_PER_PAGE} from '../constants';
-import {renderGroup, renderNumber, renderQuota, renderText, stringToColor} from '../helpers/render';
-import AddUser from "../pages/User/AddUser";
-import EditUser from "../pages/User/EditUser";
+import React, { useEffect, useState } from 'react';
+import { API, showError, showSuccess } from '../helpers';
+import { Button, Form, Popconfirm, Space, Table, Tag, Tooltip } from '@douyinfe/semi-ui';
+import { ITEMS_PER_PAGE } from '../constants';
+import { renderGroup, renderNumber, renderQuota } from '../helpers/render';
+import AddUser from '../pages/User/AddUser';
+import EditUser from '../pages/User/EditUser';
function renderRole(role) {
- switch (role) {
- case 1:
- return 普通用户;
- case 10:
- return 管理员;
- case 100:
- return 超级管理员;
- default:
- return 未知身份;
- }
+ switch (role) {
+ case 1:
+ return 普通用户;
+ case 10:
+ return 管理员;
+ case 100:
+ return 超级管理员;
+ default:
+ return 未知身份;
+ }
}
const UsersTable = () => {
- const columns = [{
- title: 'ID', dataIndex: 'id',
- }, {
- title: '用户名', dataIndex: 'username',
- }, {
- title: '分组', dataIndex: 'group', render: (text, record, index) => {
- return (
- {renderGroup(text)}
-
);
- },
- }, {
- title: '统计信息', dataIndex: 'info', render: (text, record, index) => {
- return (
-
-
- {renderQuota(record.quota)}
-
-
- {renderQuota(record.used_quota)}
-
-
- {renderNumber(record.request_count)}
-
-
-
);
- }
- }, {
- title: '邀请信息', dataIndex: 'invite', render: (text, record, index) => {
- return (
-
-
- {renderNumber(record.aff_count)}
-
-
- {renderQuota(record.aff_history_quota)}
-
-
- {record.inviter_id === 0 ? 无 :
- {record.inviter_id}}
-
-
-
);
- }
- }, {
- title: '角色', dataIndex: 'role', render: (text, record, index) => {
- return (
- {renderRole(text)}
-
);
- },
- }, {
- title: '状态', dataIndex: 'status', render: (text, record, index) => {
- return (
- {record.DeletedAt !== null? 已注销 : renderStatus(text)}
-
);
- },
- }, {
- title: '', dataIndex: 'operate', render: (text, record, index) => (
- {
- record.DeletedAt !== null ? <>>:
- <>
-
{
- manageUser(record.username, 'promote', record)
- }}
- >
- 提升
-
-
{
- manageUser(record.username, 'demote', record)
- }}
- >
- 降级
-
- {record.status === 1 ?
-
{
- manageUser(record.username, 'disable', record)
- }}>禁用 :
-
{
- manageUser(record.username, 'enable', record);
- }} disabled={record.status === 3}>启用}
-
{
- setEditingUser(record);
- setShowEditUser(true);
- }}>编辑
- >
-
- }
+ const columns = [{
+ title: 'ID', dataIndex: 'id'
+ }, {
+ title: '用户名', dataIndex: 'username'
+ }, {
+ title: '分组', dataIndex: 'group', render: (text, record, index) => {
+ return (
+ {renderGroup(text)}
+
);
+ }
+ }, {
+ title: '统计信息', dataIndex: 'info', render: (text, record, index) => {
+ return (
+
+
+ {renderQuota(record.quota)}
+
+
+ {renderQuota(record.used_quota)}
+
+
+ {renderNumber(record.request_count)}
+
+
+
);
+ }
+ }, {
+ title: '邀请信息', dataIndex: 'invite', render: (text, record, index) => {
+ return (
+
+
+ {renderNumber(record.aff_count)}
+
+
+ {renderQuota(record.aff_history_quota)}
+
+
+ {record.inviter_id === 0 ? 无 :
+ {record.inviter_id}}
+
+
+
);
+ }
+ }, {
+ title: '角色', dataIndex: 'role', render: (text, record, index) => {
+ return (
+ {renderRole(text)}
+
);
+ }
+ }, {
+ title: '状态', dataIndex: 'status', render: (text, record, index) => {
+ return (
+ {record.DeletedAt !== null ? 已注销 : renderStatus(text)}
+
);
+ }
+ }, {
+ title: '', dataIndex: 'operate', render: (text, record, index) => (
+ {
+ record.DeletedAt !== null ? <>> :
+ <>
{
- manageUser(record.username, 'delete', record).then(() => {
- removeRecord(record.id);
- })
- }}
+ title="确定?"
+ okType={'warning'}
+ onConfirm={() => {
+ manageUser(record.username, 'promote', record);
+ }}
>
- 删除
+ 提升
-
),
- },];
+
{
+ manageUser(record.username, 'demote', record);
+ }}
+ >
+ 降级
+
+ {record.status === 1 ?
+
{
+ manageUser(record.username, 'disable', record);
+ }}>禁用 :
+
{
+ manageUser(record.username, 'enable', record);
+ }} disabled={record.status === 3}>启用}
+
{
+ setEditingUser(record);
+ setShowEditUser(true);
+ }}>编辑
+ >
- const [users, setUsers] = useState([]);
- const [loading, setLoading] = useState(true);
- const [activePage, setActivePage] = useState(1);
- const [searchKeyword, setSearchKeyword] = useState('');
- const [searching, setSearching] = useState(false);
- const [userCount, setUserCount] = useState(ITEMS_PER_PAGE);
- const [showAddUser, setShowAddUser] = useState(false);
- const [showEditUser, setShowEditUser] = useState(false);
- const [editingUser, setEditingUser] = useState({
- id: undefined,
+ }
+
{
+ manageUser(record.username, 'delete', record).then(() => {
+ removeRecord(record.id);
+ });
+ }}
+ >
+ 删除
+
+
)
+ }];
+
+ const [users, setUsers] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [activePage, setActivePage] = useState(1);
+ const [searchKeyword, setSearchKeyword] = useState('');
+ const [searching, setSearching] = useState(false);
+ const [userCount, setUserCount] = useState(ITEMS_PER_PAGE);
+ const [showAddUser, setShowAddUser] = useState(false);
+ const [showEditUser, setShowEditUser] = useState(false);
+ const [editingUser, setEditingUser] = useState({
+ id: undefined
+ });
+
+ const setCount = (data) => {
+ if (data.length >= (activePage) * ITEMS_PER_PAGE) {
+ setUserCount(data.length + 1);
+ } else {
+ setUserCount(data.length);
+ }
+ };
+
+ const removeRecord = key => {
+ console.log(key);
+ let newDataSource = [...users];
+ if (key != null) {
+ let idx = newDataSource.findIndex(data => data.id === key);
+
+ if (idx > -1) {
+ newDataSource.splice(idx, 1);
+ setUsers(newDataSource);
+ }
+ }
+ };
+
+ const loadUsers = async (startIdx) => {
+ const res = await API.get(`/api/user/?p=${startIdx}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ if (startIdx === 0) {
+ setUsers(data);
+ setCount(data);
+ } else {
+ let newUsers = users;
+ newUsers.push(...data);
+ setUsers(newUsers);
+ setCount(newUsers);
+ }
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
+
+ const onPaginationChange = (e, { activePage }) => {
+ (async () => {
+ if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
+ // In this case we have to load more data and then append them.
+ await loadUsers(activePage - 1);
+ }
+ setActivePage(activePage);
+ })();
+ };
+
+ useEffect(() => {
+ loadUsers(0)
+ .then()
+ .catch((reason) => {
+ showError(reason);
+ });
+ }, []);
+
+ const manageUser = async (username, action, record) => {
+ const res = await API.post('/api/user/manage', {
+ username, action
});
+ const { success, message } = res.data;
+ if (success) {
+ showSuccess('操作成功完成!');
+ let user = res.data.data;
+ let newUsers = [...users];
+ if (action === 'delete') {
- const setCount = (data) => {
- if (data.length >= (activePage) * ITEMS_PER_PAGE) {
- setUserCount(data.length + 1);
- } else {
- setUserCount(data.length);
- }
+ } else {
+ record.status = user.status;
+ record.role = user.role;
+ }
+ setUsers(newUsers);
+ } else {
+ showError(message);
}
+ };
- const removeRecord = key => {
- console.log(key);
- let newDataSource = [...users];
- if (key != null) {
- let idx = newDataSource.findIndex(data => data.id === key);
-
- if (idx > -1) {
- newDataSource.splice(idx, 1);
- setUsers(newDataSource);
- }
- }
- };
-
- const loadUsers = async (startIdx) => {
- const res = await API.get(`/api/user/?p=${startIdx}`);
- const {success, message, data} = res.data;
- if (success) {
- if (startIdx === 0) {
- setUsers(data);
- setCount(data);
- } else {
- let newUsers = users;
- newUsers.push(...data);
- setUsers(newUsers);
- setCount(newUsers);
- }
- } else {
- showError(message);
- }
- setLoading(false);
- };
-
- const onPaginationChange = (e, {activePage}) => {
- (async () => {
- if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
- // In this case we have to load more data and then append them.
- await loadUsers(activePage - 1);
- }
- setActivePage(activePage);
- })();
- };
-
- useEffect(() => {
- loadUsers(0)
- .then()
- .catch((reason) => {
- showError(reason);
- });
- }, []);
-
- const manageUser = async (username, action, record) => {
- const res = await API.post('/api/user/manage', {
- username, action
- });
- const {success, message} = res.data;
- if (success) {
- showSuccess('操作成功完成!');
- let user = res.data.data;
- let newUsers = [...users];
- if (action === 'delete') {
-
- } else {
- record.status = user.status;
- record.role = user.role;
- }
- setUsers(newUsers);
- } else {
- showError(message);
- }
- };
-
- const renderStatus = (status) => {
- switch (status) {
- case 1:
- return 已激活;
- case 2:
- return (
- 已封禁
- );
- default:
- return (
- 未知状态
- );
- }
- };
-
- const searchUsers = async () => {
- if (searchKeyword === '') {
- // if keyword is blank, load files instead.
- await loadUsers(0);
- setActivePage(1);
- return;
- }
- setSearching(true);
- const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
- const {success, message, data} = res.data;
- if (success) {
- setUsers(data);
- setActivePage(1);
- } else {
- showError(message);
- }
- setSearching(false);
- };
-
- const handleKeywordChange = async (value) => {
- setSearchKeyword(value.trim());
- };
-
- const sortUser = (key) => {
- if (users.length === 0) return;
- setLoading(true);
- let sortedUsers = [...users];
- sortedUsers.sort((a, b) => {
- return ('' + a[key]).localeCompare(b[key]);
- });
- if (sortedUsers[0].id === users[0].id) {
- sortedUsers.reverse();
- }
- setUsers(sortedUsers);
- setLoading(false);
- };
-
- const handlePageChange = page => {
- setActivePage(page);
- if (page === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
- // In this case we have to load more data and then append them.
- loadUsers(page - 1).then(r => {
- });
- }
- };
-
- const pageData = users.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE);
-
- const closeAddUser = () => {
- setShowAddUser(false);
+ const renderStatus = (status) => {
+ switch (status) {
+ case 1:
+ return 已激活;
+ case 2:
+ return (
+ 已封禁
+ );
+ default:
+ return (
+ 未知状态
+ );
}
+ };
- const closeEditUser = () => {
- setShowEditUser(false);
- setEditingUser({
- id: undefined,
- });
+ const searchUsers = async () => {
+ if (searchKeyword === '') {
+ // if keyword is blank, load files instead.
+ await loadUsers(0);
+ setActivePage(1);
+ return;
}
+ setSearching(true);
+ const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ setUsers(data);
+ setActivePage(1);
+ } else {
+ showError(message);
+ }
+ setSearching(false);
+ };
- const refresh = async () => {
- if (searchKeyword === '') {
- await loadUsers(activePage - 1);
- } else {
- await searchUsers();
+ const handleKeywordChange = async (value) => {
+ setSearchKeyword(value.trim());
+ };
+
+ const sortUser = (key) => {
+ if (users.length === 0) return;
+ setLoading(true);
+ let sortedUsers = [...users];
+ sortedUsers.sort((a, b) => {
+ return ('' + a[key]).localeCompare(b[key]);
+ });
+ if (sortedUsers[0].id === users[0].id) {
+ sortedUsers.reverse();
+ }
+ setUsers(sortedUsers);
+ setLoading(false);
+ };
+
+ const handlePageChange = page => {
+ setActivePage(page);
+ if (page === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
+ // In this case we have to load more data and then append them.
+ loadUsers(page - 1).then(r => {
+ });
+ }
+ };
+
+ const pageData = users.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE);
+
+ const closeAddUser = () => {
+ setShowAddUser(false);
+ };
+
+ const closeEditUser = () => {
+ setShowEditUser(false);
+ setEditingUser({
+ id: undefined
+ });
+ };
+
+ const refresh = async () => {
+ if (searchKeyword === '') {
+ await loadUsers(activePage - 1);
+ } else {
+ await searchUsers();
+ }
+ };
+
+ return (
+ <>
+
+
+
+ handleKeywordChange(value)}
+ />
+
+
+
+ {
+ setShowAddUser(true);
}
- };
-
- return (
- <>
-
-
-
- handleKeywordChange(value)}
- />
-
-
-
- {
- setShowAddUser(true);
- }
- }>添加用户
- >
- );
+ }>添加用户
+ >
+ );
};
export default UsersTable;
diff --git a/web/src/components/WeChatIcon.js b/web/src/components/WeChatIcon.js
index 8d7f6dd..22210d9 100644
--- a/web/src/components/WeChatIcon.js
+++ b/web/src/components/WeChatIcon.js
@@ -3,14 +3,14 @@ import { Icon } from '@douyinfe/semi-ui';
const WeChatIcon = () => {
function CustomIcon() {
- return