fix: run prettier
This commit is contained in:
parent
0424baef6a
commit
caabdd1e21
@ -18,4 +18,4 @@ Before you start editing, make sure your `Actions on Save` options have `Optimiz
|
|||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
1. https://github.com/OIerDb-ng/OIerDb
|
1. https://github.com/OIerDb-ng/OIerDb
|
||||||
2. https://github.com/cornflourblue/react-hooks-redux-registration-login-example
|
2. https://github.com/cornflourblue/react-hooks-redux-registration-login-example
|
||||||
|
12
web/package-lock.json
generated
12
web/package-lock.json
generated
@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "3.0.0",
|
||||||
"react-scripts": "^5.0.1"
|
"react-scripts": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -14575,15 +14575,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "2.8.8",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
|
||||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
"integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin-prettier.js"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.13.0"
|
"node": ">=14"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "3.0.0",
|
||||||
"react-scripts": "^5.0.1"
|
"react-scripts": "^5.0.1"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
@ -1,30 +1,43 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Label, Pagination, Popup, Table } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Label,
|
||||||
|
Pagination,
|
||||||
|
Popup,
|
||||||
|
Table,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { API, showError, showInfo, showSuccess, timestamp2string } from '../helpers';
|
import {
|
||||||
|
API,
|
||||||
|
showError,
|
||||||
|
showInfo,
|
||||||
|
showSuccess,
|
||||||
|
timestamp2string,
|
||||||
|
} from '../helpers';
|
||||||
|
|
||||||
import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants';
|
import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants';
|
||||||
import { renderGroup, renderNumber } from '../helpers/render';
|
import { renderGroup, renderNumber } from '../helpers/render';
|
||||||
|
|
||||||
function renderTimestamp(timestamp) {
|
function renderTimestamp(timestamp) {
|
||||||
return (
|
return <>{timestamp2string(timestamp)}</>;
|
||||||
<>
|
|
||||||
{timestamp2string(timestamp)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let type2label = undefined;
|
let type2label = undefined;
|
||||||
|
|
||||||
function renderType(type) {
|
function renderType(type) {
|
||||||
if (!type2label) {
|
if (!type2label) {
|
||||||
type2label = new Map;
|
type2label = new Map();
|
||||||
for (let i = 0; i < CHANNEL_OPTIONS.length; i++) {
|
for (let i = 0; i < CHANNEL_OPTIONS.length; i++) {
|
||||||
type2label[CHANNEL_OPTIONS[i].value] = CHANNEL_OPTIONS[i];
|
type2label[CHANNEL_OPTIONS[i].value] = CHANNEL_OPTIONS[i];
|
||||||
}
|
}
|
||||||
type2label[0] = { value: 0, text: '未知类型', color: 'grey' };
|
type2label[0] = { value: 0, text: '未知类型', color: 'grey' };
|
||||||
}
|
}
|
||||||
return <Label basic color={type2label[type].color}>{type2label[type].text}</Label>;
|
return (
|
||||||
|
<Label basic color={type2label[type].color}>
|
||||||
|
{type2label[type].text}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderBalance(type, balance) {
|
function renderBalance(type, balance) {
|
||||||
@ -132,7 +145,11 @@ const ChannelsTable = () => {
|
|||||||
const renderStatus = (status) => {
|
const renderStatus = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 1:
|
case 1:
|
||||||
return <Label basic color='green'>已启用</Label>;
|
return (
|
||||||
|
<Label basic color='green'>
|
||||||
|
已启用
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 2:
|
case 2:
|
||||||
return (
|
return (
|
||||||
<Label basic color='red'>
|
<Label basic color='red'>
|
||||||
@ -152,15 +169,35 @@ const ChannelsTable = () => {
|
|||||||
let time = responseTime / 1000;
|
let time = responseTime / 1000;
|
||||||
time = time.toFixed(2) + ' 秒';
|
time = time.toFixed(2) + ' 秒';
|
||||||
if (responseTime === 0) {
|
if (responseTime === 0) {
|
||||||
return <Label basic color='grey'>未测试</Label>;
|
return (
|
||||||
|
<Label basic color='grey'>
|
||||||
|
未测试
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
} else if (responseTime <= 1000) {
|
} else if (responseTime <= 1000) {
|
||||||
return <Label basic color='green'>{time}</Label>;
|
return (
|
||||||
|
<Label basic color='green'>
|
||||||
|
{time}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
} else if (responseTime <= 3000) {
|
} else if (responseTime <= 3000) {
|
||||||
return <Label basic color='olive'>{time}</Label>;
|
return (
|
||||||
|
<Label basic color='olive'>
|
||||||
|
{time}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
} else if (responseTime <= 5000) {
|
} else if (responseTime <= 5000) {
|
||||||
return <Label basic color='yellow'>{time}</Label>;
|
return (
|
||||||
|
<Label basic color='yellow'>
|
||||||
|
{time}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <Label basic color='red'>{time}</Label>;
|
return (
|
||||||
|
<Label basic color='red'>
|
||||||
|
{time}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -342,7 +379,7 @@ const ChannelsTable = () => {
|
|||||||
{channels
|
{channels
|
||||||
.slice(
|
.slice(
|
||||||
(activePage - 1) * ITEMS_PER_PAGE,
|
(activePage - 1) * ITEMS_PER_PAGE,
|
||||||
activePage * ITEMS_PER_PAGE
|
activePage * ITEMS_PER_PAGE,
|
||||||
)
|
)
|
||||||
.map((channel, idx) => {
|
.map((channel, idx) => {
|
||||||
if (channel.deleted) return <></>;
|
if (channel.deleted) return <></>;
|
||||||
@ -355,7 +392,11 @@ const ChannelsTable = () => {
|
|||||||
<Table.Cell>{renderStatus(channel.status)}</Table.Cell>
|
<Table.Cell>{renderStatus(channel.status)}</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Popup
|
<Popup
|
||||||
content={channel.test_time ? renderTimestamp(channel.test_time) : '未测试'}
|
content={
|
||||||
|
channel.test_time
|
||||||
|
? renderTimestamp(channel.test_time)
|
||||||
|
: '未测试'
|
||||||
|
}
|
||||||
key={channel.id}
|
key={channel.id}
|
||||||
trigger={renderResponseTime(channel.response_time)}
|
trigger={renderResponseTime(channel.response_time)}
|
||||||
basic
|
basic
|
||||||
@ -363,7 +404,11 @@ const ChannelsTable = () => {
|
|||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Popup
|
<Popup
|
||||||
content={channel.balance_updated_time ? renderTimestamp(channel.balance_updated_time) : '未更新'}
|
content={
|
||||||
|
channel.balance_updated_time
|
||||||
|
? renderTimestamp(channel.balance_updated_time)
|
||||||
|
: '未更新'
|
||||||
|
}
|
||||||
key={channel.id}
|
key={channel.id}
|
||||||
trigger={renderBalance(channel.type, channel.balance)}
|
trigger={renderBalance(channel.type, channel.balance)}
|
||||||
basic
|
basic
|
||||||
@ -415,7 +460,7 @@ const ChannelsTable = () => {
|
|||||||
manageChannel(
|
manageChannel(
|
||||||
channel.id,
|
channel.id,
|
||||||
channel.status === 1 ? 'disable' : 'enable',
|
channel.status === 1 ? 'disable' : 'enable',
|
||||||
idx
|
idx,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -438,14 +483,24 @@ const ChannelsTable = () => {
|
|||||||
<Table.Footer>
|
<Table.Footer>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.HeaderCell colSpan='8'>
|
<Table.HeaderCell colSpan='8'>
|
||||||
<Button size='small' as={Link} to='/channel/add' loading={loading}>
|
<Button
|
||||||
|
size='small'
|
||||||
|
as={Link}
|
||||||
|
to='/channel/add'
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
添加新的渠道
|
添加新的渠道
|
||||||
</Button>
|
</Button>
|
||||||
<Button size='small' loading={loading} onClick={testAllChannels}>
|
<Button size='small' loading={loading} onClick={testAllChannels}>
|
||||||
测试所有已启用通道
|
测试所有已启用通道
|
||||||
</Button>
|
</Button>
|
||||||
<Button size='small' onClick={updateAllChannelsBalance}
|
<Button
|
||||||
loading={loading || updatingBalance}>更新所有已启用通道余额</Button>
|
size='small'
|
||||||
|
onClick={updateAllChannelsBalance}
|
||||||
|
loading={loading || updatingBalance}
|
||||||
|
>
|
||||||
|
更新所有已启用通道余额
|
||||||
|
</Button>
|
||||||
<Pagination
|
<Pagination
|
||||||
floated='right'
|
floated='right'
|
||||||
activePage={activePage}
|
activePage={activePage}
|
||||||
@ -457,7 +512,9 @@ const ChannelsTable = () => {
|
|||||||
(channels.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
|
(channels.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
<Button size='small' onClick={refresh} loading={loading}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
</Table.HeaderCell>
|
</Table.HeaderCell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Footer>
|
</Table.Footer>
|
||||||
|
@ -37,10 +37,7 @@ const Footer = () => {
|
|||||||
></div>
|
></div>
|
||||||
) : (
|
) : (
|
||||||
<div className='custom-footer'>
|
<div className='custom-footer'>
|
||||||
<a
|
<a href='https://github.com/songquanpeng/one-api' target='_blank'>
|
||||||
href='https://github.com/songquanpeng/one-api'
|
|
||||||
target='_blank'
|
|
||||||
>
|
|
||||||
{systemName} {process.env.REACT_APP_VERSION}{' '}
|
{systemName} {process.env.REACT_APP_VERSION}{' '}
|
||||||
</a>
|
</a>
|
||||||
由{' '}
|
由{' '}
|
||||||
|
@ -2,8 +2,22 @@ import React, { useContext, useState } from 'react';
|
|||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { UserContext } from '../context/User';
|
import { UserContext } from '../context/User';
|
||||||
|
|
||||||
import { Button, Container, Dropdown, Icon, Menu, Segment } from 'semantic-ui-react';
|
import {
|
||||||
import { API, getLogo, getSystemName, isAdmin, isMobile, showSuccess } from '../helpers';
|
Button,
|
||||||
|
Container,
|
||||||
|
Dropdown,
|
||||||
|
Icon,
|
||||||
|
Menu,
|
||||||
|
Segment,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
|
import {
|
||||||
|
API,
|
||||||
|
getLogo,
|
||||||
|
getSystemName,
|
||||||
|
isAdmin,
|
||||||
|
isMobile,
|
||||||
|
showSuccess,
|
||||||
|
} from '../helpers';
|
||||||
import '../index.css';
|
import '../index.css';
|
||||||
|
|
||||||
// Header Buttons
|
// Header Buttons
|
||||||
@ -11,58 +25,58 @@ let headerButtons = [
|
|||||||
{
|
{
|
||||||
name: '首页',
|
name: '首页',
|
||||||
to: '/',
|
to: '/',
|
||||||
icon: 'home'
|
icon: 'home',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '渠道',
|
name: '渠道',
|
||||||
to: '/channel',
|
to: '/channel',
|
||||||
icon: 'sitemap',
|
icon: 'sitemap',
|
||||||
admin: true
|
admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '令牌',
|
name: '令牌',
|
||||||
to: '/token',
|
to: '/token',
|
||||||
icon: 'key'
|
icon: 'key',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '兑换',
|
name: '兑换',
|
||||||
to: '/redemption',
|
to: '/redemption',
|
||||||
icon: 'dollar sign',
|
icon: 'dollar sign',
|
||||||
admin: true
|
admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '充值',
|
name: '充值',
|
||||||
to: '/topup',
|
to: '/topup',
|
||||||
icon: 'cart'
|
icon: 'cart',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '用户',
|
name: '用户',
|
||||||
to: '/user',
|
to: '/user',
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
admin: true
|
admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '日志',
|
name: '日志',
|
||||||
to: '/log',
|
to: '/log',
|
||||||
icon: 'book'
|
icon: 'book',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '设置',
|
name: '设置',
|
||||||
to: '/setting',
|
to: '/setting',
|
||||||
icon: 'setting'
|
icon: 'setting',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '关于',
|
name: '关于',
|
||||||
to: '/about',
|
to: '/about',
|
||||||
icon: 'info circle'
|
icon: 'info circle',
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (localStorage.getItem('chat_link')) {
|
if (localStorage.getItem('chat_link')) {
|
||||||
headerButtons.splice(1, 0, {
|
headerButtons.splice(1, 0, {
|
||||||
name: '聊天',
|
name: '聊天',
|
||||||
to: '/chat',
|
to: '/chat',
|
||||||
icon: 'comments'
|
icon: 'comments',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,21 +134,17 @@ const Header = () => {
|
|||||||
style={
|
style={
|
||||||
showSidebar
|
showSidebar
|
||||||
? {
|
? {
|
||||||
borderBottom: 'none',
|
borderBottom: 'none',
|
||||||
marginBottom: '0',
|
marginBottom: '0',
|
||||||
borderTop: 'none',
|
borderTop: 'none',
|
||||||
height: '51px'
|
height: '51px',
|
||||||
}
|
}
|
||||||
: { borderTop: 'none', height: '52px' }
|
: { borderTop: 'none', height: '52px' }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Container>
|
<Container>
|
||||||
<Menu.Item as={Link} to='/'>
|
<Menu.Item as={Link} to='/'>
|
||||||
<img
|
<img src={logo} alt='logo' style={{ marginRight: '0.75em' }} />
|
||||||
src={logo}
|
|
||||||
alt='logo'
|
|
||||||
style={{ marginRight: '0.75em' }}
|
|
||||||
/>
|
|
||||||
<div style={{ fontSize: '20px' }}>
|
<div style={{ fontSize: '20px' }}>
|
||||||
<b>{systemName}</b>
|
<b>{systemName}</b>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +34,7 @@ const LoginForm = () => {
|
|||||||
const logo = getLogo();
|
const logo = getLogo();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchParams.get("expired")) {
|
if (searchParams.get('expired')) {
|
||||||
showError('未登录或登录已过期,请重新登录!');
|
showError('未登录或登录已过期,请重新登录!');
|
||||||
}
|
}
|
||||||
let status = localStorage.getItem('status');
|
let status = localStorage.getItem('status');
|
||||||
@ -53,13 +53,13 @@ const LoginForm = () => {
|
|||||||
|
|
||||||
const onGitHubOAuthClicked = () => {
|
const onGitHubOAuthClicked = () => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`
|
`https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDiscordOAuthClicked = () => {
|
const onDiscordOAuthClicked = () => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://discord.com/oauth2/authorize?response_type=code&client_id=${status.discord_client_id}&redirect_uri=${window.location.origin}/oauth/discord&scope=identify`
|
`https://discord.com/oauth2/authorize?response_type=code&client_id=${status.discord_client_id}&redirect_uri=${window.location.origin}/oauth/discord&scope=identify`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ const LoginForm = () => {
|
|||||||
|
|
||||||
const onSubmitWeChatVerificationCode = async () => {
|
const onSubmitWeChatVerificationCode = async () => {
|
||||||
const res = await API.get(
|
const res = await API.get(
|
||||||
`/api/oauth/wechat?code=${inputs.wechat_verification_code}`
|
`/api/oauth/wechat?code=${inputs.wechat_verification_code}`,
|
||||||
);
|
);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -96,10 +96,13 @@ const LoginForm = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await API.post(`/api/user/login?turnstile=${turnstileToken}`, {
|
const res = await API.post(
|
||||||
username,
|
`/api/user/login?turnstile=${turnstileToken}`,
|
||||||
password,
|
{
|
||||||
});
|
username,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
userDispatch({ type: 'login', payload: data });
|
userDispatch({ type: 'login', payload: data });
|
||||||
@ -113,29 +116,29 @@ const LoginForm = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid textAlign="center" style={{ marginTop: '48px' }}>
|
<Grid textAlign='center' style={{ marginTop: '48px' }}>
|
||||||
<Grid.Column style={{ maxWidth: 450 }}>
|
<Grid.Column style={{ maxWidth: 450 }}>
|
||||||
<Header as="h2" color="" textAlign="center">
|
<Header as='h2' color='' textAlign='center'>
|
||||||
<Image src={logo} /> 用户登录
|
<Image src={logo} /> 用户登录
|
||||||
</Header>
|
</Header>
|
||||||
<Form size="large">
|
<Form size='large'>
|
||||||
<Segment>
|
<Segment>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
fluid
|
fluid
|
||||||
icon="user"
|
icon='user'
|
||||||
iconPosition="left"
|
iconPosition='left'
|
||||||
placeholder="用户名"
|
placeholder='用户名'
|
||||||
name="username"
|
name='username'
|
||||||
value={username}
|
value={username}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
fluid
|
fluid
|
||||||
icon="lock"
|
icon='lock'
|
||||||
iconPosition="left"
|
iconPosition='left'
|
||||||
placeholder="密码"
|
placeholder='密码'
|
||||||
name="password"
|
name='password'
|
||||||
type="password"
|
type='password'
|
||||||
value={password}
|
value={password}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
@ -149,18 +152,18 @@ const LoginForm = () => {
|
|||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
<Button color="" fluid size="large" onClick={handleSubmit}>
|
<Button color='' fluid size='large' onClick={handleSubmit}>
|
||||||
登录
|
登录
|
||||||
</Button>
|
</Button>
|
||||||
</Segment>
|
</Segment>
|
||||||
</Form>
|
</Form>
|
||||||
<Message>
|
<Message>
|
||||||
忘记密码?
|
忘记密码?
|
||||||
<Link to="/reset" className="btn btn-link">
|
<Link to='/reset' className='btn btn-link'>
|
||||||
点击重置
|
点击重置
|
||||||
</Link>
|
</Link>
|
||||||
; 没有账户?
|
; 没有账户?
|
||||||
<Link to="/register" className="btn btn-link">
|
<Link to='/register' className='btn btn-link'>
|
||||||
点击注册
|
点击注册
|
||||||
</Link>
|
</Link>
|
||||||
</Message>
|
</Message>
|
||||||
@ -170,24 +173,24 @@ const LoginForm = () => {
|
|||||||
{status.discord_oauth && (
|
{status.discord_oauth && (
|
||||||
<Button
|
<Button
|
||||||
circular
|
circular
|
||||||
color="blue"
|
color='blue'
|
||||||
icon="discord"
|
icon='discord'
|
||||||
onClick={onDiscordOAuthClicked}
|
onClick={onDiscordOAuthClicked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{status.github_oauth && (
|
{status.github_oauth && (
|
||||||
<Button
|
<Button
|
||||||
circular
|
circular
|
||||||
color="black"
|
color='black'
|
||||||
icon="github"
|
icon='github'
|
||||||
onClick={onGitHubOAuthClicked}
|
onClick={onGitHubOAuthClicked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{status.wechat_login && (
|
{status.wechat_login && (
|
||||||
<Button
|
<Button
|
||||||
circular
|
circular
|
||||||
color="green"
|
color='green'
|
||||||
icon="wechat"
|
icon='wechat'
|
||||||
onClick={onWeChatLoginClicked}
|
onClick={onWeChatLoginClicked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -209,18 +212,18 @@ const LoginForm = () => {
|
|||||||
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
|
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Form size="large">
|
<Form size='large'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
fluid
|
fluid
|
||||||
placeholder="验证码"
|
placeholder='验证码'
|
||||||
name="wechat_verification_code"
|
name='wechat_verification_code'
|
||||||
value={inputs.wechat_verification_code}
|
value={inputs.wechat_verification_code}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
color=""
|
color=''
|
||||||
fluid
|
fluid
|
||||||
size="large"
|
size='large'
|
||||||
onClick={onSubmitWeChatVerificationCode}
|
onClick={onSubmitWeChatVerificationCode}
|
||||||
>
|
>
|
||||||
登录
|
登录
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Header, Label, Pagination, Segment, Select, Table } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Header,
|
||||||
|
Label,
|
||||||
|
Pagination,
|
||||||
|
Segment,
|
||||||
|
Select,
|
||||||
|
Table,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { API, isAdmin, showError, timestamp2string } from '../helpers';
|
import { API, isAdmin, showError, timestamp2string } from '../helpers';
|
||||||
|
|
||||||
import { ITEMS_PER_PAGE } from '../constants';
|
import { ITEMS_PER_PAGE } from '../constants';
|
||||||
import { renderQuota } from '../helpers/render';
|
import { renderQuota } from '../helpers/render';
|
||||||
|
|
||||||
function renderTimestamp(timestamp) {
|
function renderTimestamp(timestamp) {
|
||||||
return (
|
return <>{timestamp2string(timestamp)}</>;
|
||||||
<>
|
|
||||||
{timestamp2string(timestamp)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MODE_OPTIONS = [
|
const MODE_OPTIONS = [
|
||||||
{ key: 'all', text: '全部用户', value: 'all' },
|
{ key: 'all', text: '全部用户', value: 'all' },
|
||||||
{ key: 'self', text: '当前用户', value: 'self' }
|
{ key: 'self', text: '当前用户', value: 'self' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const LOG_OPTIONS = [
|
const LOG_OPTIONS = [
|
||||||
@ -23,21 +28,46 @@ const LOG_OPTIONS = [
|
|||||||
{ key: '1', text: '充值', value: 1 },
|
{ key: '1', text: '充值', value: 1 },
|
||||||
{ key: '2', text: '消费', value: 2 },
|
{ key: '2', text: '消费', value: 2 },
|
||||||
{ key: '3', text: '管理', value: 3 },
|
{ key: '3', text: '管理', value: 3 },
|
||||||
{ key: '4', text: '系统', value: 4 }
|
{ key: '4', text: '系统', value: 4 },
|
||||||
];
|
];
|
||||||
|
|
||||||
function renderType(type) {
|
function renderType(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 1:
|
case 1:
|
||||||
return <Label basic color='green'> 充值 </Label>;
|
return (
|
||||||
|
<Label basic color='green'>
|
||||||
|
{' '}
|
||||||
|
充值{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 2:
|
case 2:
|
||||||
return <Label basic color='olive'> 消费 </Label>;
|
return (
|
||||||
|
<Label basic color='olive'>
|
||||||
|
{' '}
|
||||||
|
消费{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 3:
|
case 3:
|
||||||
return <Label basic color='orange'> 管理 </Label>;
|
return (
|
||||||
|
<Label basic color='orange'>
|
||||||
|
{' '}
|
||||||
|
管理{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 4:
|
case 4:
|
||||||
return <Label basic color='purple'> 系统 </Label>;
|
return (
|
||||||
|
<Label basic color='purple'>
|
||||||
|
{' '}
|
||||||
|
系统{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return <Label basic color='black'> 未知 </Label>;
|
return (
|
||||||
|
<Label basic color='black'>
|
||||||
|
{' '}
|
||||||
|
未知{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,13 +85,14 @@ const LogsTable = () => {
|
|||||||
token_name: '',
|
token_name: '',
|
||||||
model_name: '',
|
model_name: '',
|
||||||
start_timestamp: timestamp2string(0),
|
start_timestamp: timestamp2string(0),
|
||||||
end_timestamp: timestamp2string(now.getTime() / 1000 + 3600)
|
end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
|
||||||
});
|
});
|
||||||
const { username, token_name, model_name, start_timestamp, end_timestamp } = inputs;
|
const { username, token_name, model_name, start_timestamp, end_timestamp } =
|
||||||
|
inputs;
|
||||||
|
|
||||||
const [stat, setStat] = useState({
|
const [stat, setStat] = useState({
|
||||||
quota: 0,
|
quota: 0,
|
||||||
token: 0
|
token: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleInputChange = (e, { name, value }) => {
|
const handleInputChange = (e, { name, value }) => {
|
||||||
@ -71,7 +102,9 @@ const LogsTable = () => {
|
|||||||
const getLogSelfStat = async () => {
|
const getLogSelfStat = async () => {
|
||||||
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
|
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
|
||||||
let localEndTimestamp = Date.parse(end_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}`);
|
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;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setStat(data);
|
setStat(data);
|
||||||
@ -83,7 +116,9 @@ const LogsTable = () => {
|
|||||||
const getLogStat = async () => {
|
const getLogStat = async () => {
|
||||||
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
|
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
|
||||||
let localEndTimestamp = Date.parse(end_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}`);
|
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}`,
|
||||||
|
);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setStat(data);
|
setStat(data);
|
||||||
@ -129,7 +164,7 @@ const LogsTable = () => {
|
|||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setActivePage(1)
|
setActivePage(1);
|
||||||
await loadLogs(0);
|
await loadLogs(0);
|
||||||
if (isAdminUser) {
|
if (isAdminUser) {
|
||||||
getLogStat().then();
|
getLogStat().then();
|
||||||
@ -169,7 +204,7 @@ const LogsTable = () => {
|
|||||||
if (logs.length === 0) return;
|
if (logs.length === 0) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let sortedLogs = [...logs];
|
let sortedLogs = [...logs];
|
||||||
if (typeof sortedLogs[0][key] === 'string'){
|
if (typeof sortedLogs[0][key] === 'string') {
|
||||||
sortedLogs.sort((a, b) => {
|
sortedLogs.sort((a, b) => {
|
||||||
return ('' + a[key]).localeCompare(b[key]);
|
return ('' + a[key]).localeCompare(b[key]);
|
||||||
});
|
});
|
||||||
@ -190,28 +225,61 @@ const LogsTable = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Segment>
|
<Segment>
|
||||||
<Header as='h3'>使用明细(总消耗额度:{renderQuota(stat.quota)})</Header>
|
<Header as='h3'>
|
||||||
|
使用明细(总消耗额度:{renderQuota(stat.quota)})
|
||||||
|
</Header>
|
||||||
<Form>
|
<Form>
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
{
|
{isAdminUser && (
|
||||||
isAdminUser && (
|
<Form.Input
|
||||||
<Form.Input fluid label={'用户名称'} width={2} value={username}
|
fluid
|
||||||
placeholder={'可选值'} name='username'
|
label={'用户名称'}
|
||||||
onChange={handleInputChange} />
|
width={2}
|
||||||
)
|
value={username}
|
||||||
}
|
placeholder={'可选值'}
|
||||||
<Form.Input fluid label={'令牌名称'} width={isAdminUser ? 2 : 3} value={token_name}
|
name='username'
|
||||||
placeholder={'可选值'} name='token_name' onChange={handleInputChange} />
|
onChange={handleInputChange}
|
||||||
<Form.Input fluid label='模型名称' width={isAdminUser ? 2 : 3} value={model_name} placeholder='可选值'
|
/>
|
||||||
name='model_name'
|
)}
|
||||||
onChange={handleInputChange} />
|
<Form.Input
|
||||||
<Form.Input fluid label='起始时间' width={4} value={start_timestamp} type='datetime-local'
|
fluid
|
||||||
name='start_timestamp'
|
label={'令牌名称'}
|
||||||
onChange={handleInputChange} />
|
width={isAdminUser ? 2 : 3}
|
||||||
<Form.Input fluid label='结束时间' width={4} value={end_timestamp} type='datetime-local'
|
value={token_name}
|
||||||
name='end_timestamp'
|
placeholder={'可选值'}
|
||||||
onChange={handleInputChange} />
|
name='token_name'
|
||||||
<Form.Button fluid label='操作' width={2} onClick={refresh}>查询</Form.Button>
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
label='模型名称'
|
||||||
|
width={isAdminUser ? 2 : 3}
|
||||||
|
value={model_name}
|
||||||
|
placeholder='可选值'
|
||||||
|
name='model_name'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
label='起始时间'
|
||||||
|
width={4}
|
||||||
|
value={start_timestamp}
|
||||||
|
type='datetime-local'
|
||||||
|
name='start_timestamp'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
label='结束时间'
|
||||||
|
width={4}
|
||||||
|
value={end_timestamp}
|
||||||
|
type='datetime-local'
|
||||||
|
name='end_timestamp'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<Form.Button fluid label='操作' width={2} onClick={refresh}>
|
||||||
|
查询
|
||||||
|
</Form.Button>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
</Form>
|
</Form>
|
||||||
<Table basic compact size='small'>
|
<Table basic compact size='small'>
|
||||||
@ -226,8 +294,8 @@ const LogsTable = () => {
|
|||||||
>
|
>
|
||||||
时间
|
时间
|
||||||
</Table.HeaderCell>
|
</Table.HeaderCell>
|
||||||
{
|
{isAdminUser && (
|
||||||
isAdminUser && <Table.HeaderCell
|
<Table.HeaderCell
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
sortLog('username');
|
sortLog('username');
|
||||||
@ -236,7 +304,7 @@ const LogsTable = () => {
|
|||||||
>
|
>
|
||||||
用户
|
用户
|
||||||
</Table.HeaderCell>
|
</Table.HeaderCell>
|
||||||
}
|
)}
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -307,24 +375,42 @@ const LogsTable = () => {
|
|||||||
{logs
|
{logs
|
||||||
.slice(
|
.slice(
|
||||||
(activePage - 1) * ITEMS_PER_PAGE,
|
(activePage - 1) * ITEMS_PER_PAGE,
|
||||||
activePage * ITEMS_PER_PAGE
|
activePage * ITEMS_PER_PAGE,
|
||||||
)
|
)
|
||||||
.map((log, idx) => {
|
.map((log, idx) => {
|
||||||
if (log.deleted) return <></>;
|
if (log.deleted) return <></>;
|
||||||
return (
|
return (
|
||||||
<Table.Row key={log.created_at}>
|
<Table.Row key={log.created_at}>
|
||||||
<Table.Cell>{renderTimestamp(log.created_at)}</Table.Cell>
|
<Table.Cell>{renderTimestamp(log.created_at)}</Table.Cell>
|
||||||
{
|
{isAdminUser && (
|
||||||
isAdminUser && (
|
<Table.Cell>
|
||||||
<Table.Cell>{log.username ? <Label>{log.username}</Label> : ''}</Table.Cell>
|
{log.username ? <Label>{log.username}</Label> : ''}
|
||||||
)
|
</Table.Cell>
|
||||||
}
|
)}
|
||||||
<Table.Cell>{log.token_name ? <Label basic>{log.token_name}</Label> : ''}</Table.Cell>
|
<Table.Cell>
|
||||||
|
{log.token_name ? (
|
||||||
|
<Label basic>{log.token_name}</Label>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>{renderType(log.type)}</Table.Cell>
|
<Table.Cell>{renderType(log.type)}</Table.Cell>
|
||||||
<Table.Cell>{log.model_name ? <Label basic>{log.model_name}</Label> : ''}</Table.Cell>
|
<Table.Cell>
|
||||||
<Table.Cell>{log.prompt_tokens ? log.prompt_tokens : ''}</Table.Cell>
|
{log.model_name ? (
|
||||||
<Table.Cell>{log.completion_tokens ? log.completion_tokens : ''}</Table.Cell>
|
<Label basic>{log.model_name}</Label>
|
||||||
<Table.Cell>{log.quota ? renderQuota(log.quota, 6) : ''}</Table.Cell>
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{log.prompt_tokens ? log.prompt_tokens : ''}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{log.completion_tokens ? log.completion_tokens : ''}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{log.quota ? renderQuota(log.quota, 6) : ''}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>{log.content}</Table.Cell>
|
<Table.Cell>{log.content}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
);
|
);
|
||||||
@ -344,7 +430,9 @@ const LogsTable = () => {
|
|||||||
setLogType(value);
|
setLogType(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
<Button size='small' onClick={refresh} loading={loading}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
<Pagination
|
<Pagination
|
||||||
floated='right'
|
floated='right'
|
||||||
activePage={activePage}
|
activePage={activePage}
|
||||||
|
@ -54,7 +54,7 @@ const OperationSetting = () => {
|
|||||||
}
|
}
|
||||||
const res = await API.put('/api/option/', {
|
const res = await API.put('/api/option/', {
|
||||||
key,
|
key,
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -76,11 +76,22 @@ const OperationSetting = () => {
|
|||||||
const submitConfig = async (group) => {
|
const submitConfig = async (group) => {
|
||||||
switch (group) {
|
switch (group) {
|
||||||
case 'monitor':
|
case 'monitor':
|
||||||
if (originInputs['ChannelDisableThreshold'] !== inputs.ChannelDisableThreshold) {
|
if (
|
||||||
await updateOption('ChannelDisableThreshold', inputs.ChannelDisableThreshold);
|
originInputs['ChannelDisableThreshold'] !==
|
||||||
|
inputs.ChannelDisableThreshold
|
||||||
|
) {
|
||||||
|
await updateOption(
|
||||||
|
'ChannelDisableThreshold',
|
||||||
|
inputs.ChannelDisableThreshold,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold) {
|
if (
|
||||||
await updateOption('QuotaRemindThreshold', inputs.QuotaRemindThreshold);
|
originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold
|
||||||
|
) {
|
||||||
|
await updateOption(
|
||||||
|
'QuotaRemindThreshold',
|
||||||
|
inputs.QuotaRemindThreshold,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'ratio':
|
case 'ratio':
|
||||||
@ -134,9 +145,7 @@ const OperationSetting = () => {
|
|||||||
<Grid columns={1}>
|
<Grid columns={1}>
|
||||||
<Grid.Column>
|
<Grid.Column>
|
||||||
<Form loading={loading}>
|
<Form loading={loading}>
|
||||||
<Header as='h3'>
|
<Header as='h3'>通用设置</Header>
|
||||||
通用设置
|
|
||||||
</Header>
|
|
||||||
<Form.Group widths={4}>
|
<Form.Group widths={4}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='充值链接'
|
label='充值链接'
|
||||||
@ -204,13 +213,15 @@ const OperationSetting = () => {
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
submitConfig('general').then();
|
onClick={() => {
|
||||||
}}>保存通用设置</Form.Button>
|
submitConfig('general').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存通用设置
|
||||||
|
</Form.Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Header as='h3'>
|
<Header as='h3'>监控设置</Header>
|
||||||
监控设置
|
|
||||||
</Header>
|
|
||||||
<Form.Group widths={3}>
|
<Form.Group widths={3}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='最长响应时间'
|
label='最长响应时间'
|
||||||
@ -241,13 +252,15 @@ const OperationSetting = () => {
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
submitConfig('monitor').then();
|
onClick={() => {
|
||||||
}}>保存监控设置</Form.Button>
|
submitConfig('monitor').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存监控设置
|
||||||
|
</Form.Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Header as='h3'>
|
<Header as='h3'>额度设置</Header>
|
||||||
额度设置
|
|
||||||
</Header>
|
|
||||||
<Form.Group widths={4}>
|
<Form.Group widths={4}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='新用户初始额度'
|
label='新用户初始额度'
|
||||||
@ -290,13 +303,15 @@ const OperationSetting = () => {
|
|||||||
placeholder='例如:1000'
|
placeholder='例如:1000'
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
submitConfig('quota').then();
|
onClick={() => {
|
||||||
}}>保存额度设置</Form.Button>
|
submitConfig('quota').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存额度设置
|
||||||
|
</Form.Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Header as='h3'>
|
<Header as='h3'>倍率设置</Header>
|
||||||
倍率设置
|
|
||||||
</Header>
|
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
label='模型倍率'
|
label='模型倍率'
|
||||||
@ -319,9 +334,13 @@ const OperationSetting = () => {
|
|||||||
placeholder='为一个 JSON 文本,键为分组名称,值为倍率'
|
placeholder='为一个 JSON 文本,键为分组名称,值为倍率'
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
submitConfig('ratio').then();
|
onClick={() => {
|
||||||
}}>保存倍率设置</Form.Button>
|
submitConfig('ratio').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存倍率设置
|
||||||
|
</Form.Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Divider, Form, Grid, Header, Message, Modal } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Form,
|
||||||
|
Grid,
|
||||||
|
Header,
|
||||||
|
Message,
|
||||||
|
Modal,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { API, showError, showSuccess } from '../helpers';
|
import { API, showError, showSuccess } from '../helpers';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
|
|
||||||
@ -10,13 +18,13 @@ const OtherSetting = () => {
|
|||||||
About: '',
|
About: '',
|
||||||
SystemName: '',
|
SystemName: '',
|
||||||
Logo: '',
|
Logo: '',
|
||||||
HomePageContent: ''
|
HomePageContent: '',
|
||||||
});
|
});
|
||||||
let [loading, setLoading] = useState(false);
|
let [loading, setLoading] = useState(false);
|
||||||
const [showUpdateModal, setShowUpdateModal] = useState(false);
|
const [showUpdateModal, setShowUpdateModal] = useState(false);
|
||||||
const [updateData, setUpdateData] = useState({
|
const [updateData, setUpdateData] = useState({
|
||||||
tag_name: '',
|
tag_name: '',
|
||||||
content: ''
|
content: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOptions = async () => {
|
const getOptions = async () => {
|
||||||
@ -43,7 +51,7 @@ const OtherSetting = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await API.put('/api/option/', {
|
const res = await API.put('/api/option/', {
|
||||||
key,
|
key,
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -83,13 +91,12 @@ const OtherSetting = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openGitHubRelease = () => {
|
const openGitHubRelease = () => {
|
||||||
window.location =
|
window.location = 'https://github.com/songquanpeng/one-api/releases/latest';
|
||||||
'https://github.com/songquanpeng/one-api/releases/latest';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkUpdate = async () => {
|
const checkUpdate = async () => {
|
||||||
const res = await API.get(
|
const res = await API.get(
|
||||||
'https://api.github.com/repos/songquanpeng/one-api/releases/latest'
|
'https://api.github.com/repos/songquanpeng/one-api/releases/latest',
|
||||||
);
|
);
|
||||||
const { tag_name, body } = res.data;
|
const { tag_name, body } = res.data;
|
||||||
if (tag_name === process.env.REACT_APP_VERSION) {
|
if (tag_name === process.env.REACT_APP_VERSION) {
|
||||||
@ -97,7 +104,7 @@ const OtherSetting = () => {
|
|||||||
} else {
|
} else {
|
||||||
setUpdateData({
|
setUpdateData({
|
||||||
tag_name: tag_name,
|
tag_name: tag_name,
|
||||||
content: marked.parse(body)
|
content: marked.parse(body),
|
||||||
});
|
});
|
||||||
setShowUpdateModal(true);
|
setShowUpdateModal(true);
|
||||||
}
|
}
|
||||||
@ -153,7 +160,9 @@ const OtherSetting = () => {
|
|||||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => submitOption('HomePageContent')}>保存首页内容</Form.Button>
|
<Form.Button onClick={() => submitOption('HomePageContent')}>
|
||||||
|
保存首页内容
|
||||||
|
</Form.Button>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
label='关于'
|
label='关于'
|
||||||
@ -165,7 +174,10 @@ const OtherSetting = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitAbout}>保存关于</Form.Button>
|
<Form.Button onClick={submitAbout}>保存关于</Form.Button>
|
||||||
<Message>移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。</Message>
|
<Message>
|
||||||
|
移除 One API
|
||||||
|
的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。
|
||||||
|
</Message>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='页脚'
|
label='页脚'
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-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,
|
||||||
|
showInfo,
|
||||||
|
showNotice,
|
||||||
|
showSuccess,
|
||||||
|
} from '../helpers';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
const PasswordResetConfirm = () => {
|
const PasswordResetConfirm = () => {
|
||||||
|
@ -38,7 +38,7 @@ const PasswordResetForm = () => {
|
|||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await API.get(
|
const res = await API.get(
|
||||||
`/api/reset_password?email=${email}&turnstile=${turnstileToken}`
|
`/api/reset_password?email=${email}&turnstile=${turnstileToken}`,
|
||||||
);
|
);
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -1,7 +1,23 @@
|
|||||||
import React, { useEffect, useState, useContext } from 'react';
|
import React, { useEffect, useState, useContext } from 'react';
|
||||||
import { Button, Divider, Form, Header, Image, Message, Modal, Label } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Form,
|
||||||
|
Header,
|
||||||
|
Image,
|
||||||
|
Message,
|
||||||
|
Modal,
|
||||||
|
Label,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers';
|
import {
|
||||||
|
API,
|
||||||
|
copy,
|
||||||
|
showError,
|
||||||
|
showInfo,
|
||||||
|
showNotice,
|
||||||
|
showSuccess,
|
||||||
|
} from '../helpers';
|
||||||
import Turnstile from 'react-turnstile';
|
import Turnstile from 'react-turnstile';
|
||||||
import { UserContext } from '../context/User';
|
import { UserContext } from '../context/User';
|
||||||
|
|
||||||
@ -81,12 +97,12 @@ const PersonalSetting = () => {
|
|||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const bindWeChat = async () => {
|
const bindWeChat = async () => {
|
||||||
if (inputs.wechat_verification_code === '') return;
|
if (inputs.wechat_verification_code === '') return;
|
||||||
const res = await API.get(
|
const res = await API.get(
|
||||||
`/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`
|
`/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`,
|
||||||
);
|
);
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -99,15 +115,15 @@ const PersonalSetting = () => {
|
|||||||
|
|
||||||
const openGitHubOAuth = () => {
|
const openGitHubOAuth = () => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`
|
`https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDiscordOAuth = () => {
|
const openDiscordOAuth = () => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://discord.com/api/oauth2/authorize?client_id=${status.discord_client_id}&scope=identify%20email&response_type=code&redirect_uri=${window.location.origin}/oauth/discord`
|
`https://discord.com/api/oauth2/authorize?client_id=${status.discord_client_id}&scope=identify%20email&response_type=code&redirect_uri=${window.location.origin}/oauth/discord`,
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const sendVerificationCode = async () => {
|
const sendVerificationCode = async () => {
|
||||||
if (inputs.email === '') return;
|
if (inputs.email === '') return;
|
||||||
@ -117,7 +133,7 @@ const PersonalSetting = () => {
|
|||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await API.get(
|
const res = await API.get(
|
||||||
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`
|
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`,
|
||||||
);
|
);
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -132,7 +148,7 @@ const PersonalSetting = () => {
|
|||||||
if (inputs.email_verification_code === '') return;
|
if (inputs.email_verification_code === '') return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await API.get(
|
const res = await API.get(
|
||||||
`/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}`
|
`/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}`,
|
||||||
);
|
);
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -148,29 +164,33 @@ const PersonalSetting = () => {
|
|||||||
<div style={{ lineHeight: '40px' }}>
|
<div style={{ lineHeight: '40px' }}>
|
||||||
<Header as='h3'>通用设置</Header>
|
<Header as='h3'>通用设置</Header>
|
||||||
<Message>
|
<Message>
|
||||||
注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
|
注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI
|
||||||
|
相关的服务,请知悉。
|
||||||
</Message>
|
</Message>
|
||||||
<Button as={Link} to={`/user/edit/`}>
|
<Button as={Link} to={`/user/edit/`}>
|
||||||
更新个人信息
|
更新个人信息
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={generateAccessToken}>生成系统访问令牌</Button>
|
<Button onClick={generateAccessToken}>生成系统访问令牌</Button>
|
||||||
<Button onClick={getAffLink}>复制邀请链接</Button>
|
<Button onClick={getAffLink}>复制邀请链接</Button>
|
||||||
<Button onClick={() => {
|
<Button
|
||||||
setShowAccountDeleteModal(true);
|
onClick={() => {
|
||||||
}} color='red'>删除个人账户</Button>
|
setShowAccountDeleteModal(true);
|
||||||
|
}}
|
||||||
|
color='red'
|
||||||
|
>
|
||||||
|
删除个人账户
|
||||||
|
</Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Header as='h3'>账号绑定</Header>
|
<Header as='h3'>账号绑定</Header>
|
||||||
{
|
{status.wechat_login && (
|
||||||
status.wechat_login && (
|
<Button
|
||||||
<Button
|
onClick={() => {
|
||||||
onClick={() => {
|
setShowWeChatBindModal(true);
|
||||||
setShowWeChatBindModal(true);
|
}}
|
||||||
}}
|
>
|
||||||
>
|
绑定微信账号
|
||||||
绑定微信账号
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
<Modal
|
<Modal
|
||||||
onClose={() => setShowWeChatBindModal(false)}
|
onClose={() => setShowWeChatBindModal(false)}
|
||||||
onOpen={() => setShowWeChatBindModal(true)}
|
onOpen={() => setShowWeChatBindModal(true)}
|
||||||
@ -200,16 +220,12 @@ const PersonalSetting = () => {
|
|||||||
</Modal.Description>
|
</Modal.Description>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
</Modal>
|
</Modal>
|
||||||
{
|
{status.github_oauth && (
|
||||||
status.github_oauth && (
|
<Button onClick={openGitHubOAuth}>绑定 GitHub 账号</Button>
|
||||||
<Button onClick={openGitHubOAuth}>绑定 GitHub 账号</Button>
|
)}
|
||||||
)
|
{status.discord_oauth && (
|
||||||
}
|
<Button onClick={openDiscordOAuth}>绑定 Discord 账号</Button>
|
||||||
{
|
)}
|
||||||
status.discord_oauth && (
|
|
||||||
<Button onClick={openDiscordOAuth}>绑定 Discord 账号</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowEmailBindModal(true);
|
setShowEmailBindModal(true);
|
||||||
|
@ -2,7 +2,6 @@ import { Navigate } from 'react-router-dom';
|
|||||||
|
|
||||||
import { history } from '../helpers';
|
import { history } from '../helpers';
|
||||||
|
|
||||||
|
|
||||||
function PrivateRoute({ children }) {
|
function PrivateRoute({ children }) {
|
||||||
if (!localStorage.getItem('user')) {
|
if (!localStorage.getItem('user')) {
|
||||||
return <Navigate to='/login' state={{ from: history.location }} />;
|
return <Navigate to='/login' state={{ from: history.location }} />;
|
||||||
@ -10,4 +9,4 @@ function PrivateRoute({ children }) {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { PrivateRoute };
|
export { PrivateRoute };
|
||||||
|
@ -1,29 +1,59 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Label, Message, Pagination, Table } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Label,
|
||||||
|
Message,
|
||||||
|
Pagination,
|
||||||
|
Table,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { API, copy, showError, showInfo, showSuccess, showWarning, timestamp2string } from '../helpers';
|
import {
|
||||||
|
API,
|
||||||
|
copy,
|
||||||
|
showError,
|
||||||
|
showInfo,
|
||||||
|
showSuccess,
|
||||||
|
showWarning,
|
||||||
|
timestamp2string,
|
||||||
|
} from '../helpers';
|
||||||
|
|
||||||
import { ITEMS_PER_PAGE } from '../constants';
|
import { ITEMS_PER_PAGE } from '../constants';
|
||||||
import { renderQuota } from '../helpers/render';
|
import { renderQuota } from '../helpers/render';
|
||||||
|
|
||||||
function renderTimestamp(timestamp) {
|
function renderTimestamp(timestamp) {
|
||||||
return (
|
return <>{timestamp2string(timestamp)}</>;
|
||||||
<>
|
|
||||||
{timestamp2string(timestamp)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStatus(status) {
|
function renderStatus(status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 1:
|
case 1:
|
||||||
return <Label basic color='green'>未使用</Label>;
|
return (
|
||||||
|
<Label basic color='green'>
|
||||||
|
未使用
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 2:
|
case 2:
|
||||||
return <Label basic color='red'> 已禁用 </Label>;
|
return (
|
||||||
|
<Label basic color='red'>
|
||||||
|
{' '}
|
||||||
|
已禁用{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 3:
|
case 3:
|
||||||
return <Label basic color='grey'> 已使用 </Label>;
|
return (
|
||||||
|
<Label basic color='grey'>
|
||||||
|
{' '}
|
||||||
|
已使用{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return <Label basic color='black'> 未知状态 </Label>;
|
return (
|
||||||
|
<Label basic color='black'>
|
||||||
|
{' '}
|
||||||
|
未知状态{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +140,9 @@ const RedemptionsTable = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSearching(true);
|
setSearching(true);
|
||||||
const res = await API.get(`/api/redemption/search?keyword=${searchKeyword}`);
|
const res = await API.get(
|
||||||
|
`/api/redemption/search?keyword=${searchKeyword}`,
|
||||||
|
);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setRedemptions(data);
|
setRedemptions(data);
|
||||||
@ -212,18 +244,26 @@ const RedemptionsTable = () => {
|
|||||||
{redemptions
|
{redemptions
|
||||||
.slice(
|
.slice(
|
||||||
(activePage - 1) * ITEMS_PER_PAGE,
|
(activePage - 1) * ITEMS_PER_PAGE,
|
||||||
activePage * ITEMS_PER_PAGE
|
activePage * ITEMS_PER_PAGE,
|
||||||
)
|
)
|
||||||
.map((redemption, idx) => {
|
.map((redemption, idx) => {
|
||||||
if (redemption.deleted) return <></>;
|
if (redemption.deleted) return <></>;
|
||||||
return (
|
return (
|
||||||
<Table.Row key={redemption.id}>
|
<Table.Row key={redemption.id}>
|
||||||
<Table.Cell>{redemption.id}</Table.Cell>
|
<Table.Cell>{redemption.id}</Table.Cell>
|
||||||
<Table.Cell>{redemption.name ? redemption.name : '无'}</Table.Cell>
|
<Table.Cell>
|
||||||
|
{redemption.name ? redemption.name : '无'}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>{renderStatus(redemption.status)}</Table.Cell>
|
<Table.Cell>{renderStatus(redemption.status)}</Table.Cell>
|
||||||
<Table.Cell>{renderQuota(redemption.quota)}</Table.Cell>
|
<Table.Cell>{renderQuota(redemption.quota)}</Table.Cell>
|
||||||
<Table.Cell>{renderTimestamp(redemption.created_time)}</Table.Cell>
|
<Table.Cell>
|
||||||
<Table.Cell>{redemption.redeemed_time ? renderTimestamp(redemption.redeemed_time) : "尚未兑换"} </Table.Cell>
|
{renderTimestamp(redemption.created_time)}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{redemption.redeemed_time
|
||||||
|
? renderTimestamp(redemption.redeemed_time)
|
||||||
|
: '尚未兑换'}{' '}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@ -233,7 +273,9 @@ const RedemptionsTable = () => {
|
|||||||
if (await copy(redemption.key)) {
|
if (await copy(redemption.key)) {
|
||||||
showSuccess('已复制到剪贴板!');
|
showSuccess('已复制到剪贴板!');
|
||||||
} else {
|
} else {
|
||||||
showWarning('无法复制到剪贴板,请手动复制,已将兑换码填入搜索框。')
|
showWarning(
|
||||||
|
'无法复制到剪贴板,请手动复制,已将兑换码填入搜索框。',
|
||||||
|
);
|
||||||
setSearchKeyword(redemption.key);
|
setSearchKeyword(redemption.key);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -251,12 +293,12 @@ const RedemptionsTable = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size={'small'}
|
size={'small'}
|
||||||
disabled={redemption.status === 3} // used
|
disabled={redemption.status === 3} // used
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
manageRedemption(
|
manageRedemption(
|
||||||
redemption.id,
|
redemption.id,
|
||||||
redemption.status === 1 ? 'disable' : 'enable',
|
redemption.status === 1 ? 'disable' : 'enable',
|
||||||
idx
|
idx,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -279,7 +321,12 @@ const RedemptionsTable = () => {
|
|||||||
<Table.Footer>
|
<Table.Footer>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.HeaderCell colSpan='8'>
|
<Table.HeaderCell colSpan='8'>
|
||||||
<Button size='small' as={Link} to='/redemption/add' loading={loading}>
|
<Button
|
||||||
|
size='small'
|
||||||
|
as={Link}
|
||||||
|
to='/redemption/add'
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
添加新的兑换码
|
添加新的兑换码
|
||||||
</Button>
|
</Button>
|
||||||
<Pagination
|
<Pagination
|
||||||
|
@ -73,7 +73,7 @@ const RegisterForm = () => {
|
|||||||
inputs.aff_code = affCode;
|
inputs.aff_code = affCode;
|
||||||
const res = await API.post(
|
const res = await API.post(
|
||||||
`/api/user/register?turnstile=${turnstileToken}`,
|
`/api/user/register?turnstile=${turnstileToken}`,
|
||||||
inputs
|
inputs,
|
||||||
);
|
);
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -94,7 +94,7 @@ const RegisterForm = () => {
|
|||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await API.get(
|
const res = await API.get(
|
||||||
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`
|
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`,
|
||||||
);
|
);
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -70,7 +70,7 @@ const SystemSetting = () => {
|
|||||||
}
|
}
|
||||||
const res = await API.put('/api/option/', {
|
const res = await API.put('/api/option/', {
|
||||||
key,
|
key,
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -135,7 +135,7 @@ const SystemSetting = () => {
|
|||||||
if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) {
|
if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) {
|
||||||
await updateOption(
|
await updateOption(
|
||||||
'WeChatServerAddress',
|
'WeChatServerAddress',
|
||||||
removeTrailingSlash(inputs.WeChatServerAddress)
|
removeTrailingSlash(inputs.WeChatServerAddress),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@ -144,7 +144,7 @@ const SystemSetting = () => {
|
|||||||
) {
|
) {
|
||||||
await updateOption(
|
await updateOption(
|
||||||
'WeChatAccountQRCodeImageURL',
|
'WeChatAccountQRCodeImageURL',
|
||||||
inputs.WeChatAccountQRCodeImageURL
|
inputs.WeChatAccountQRCodeImageURL,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@ -265,7 +265,9 @@ const SystemSetting = () => {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<Header as='h3'>
|
<Header as='h3'>
|
||||||
Configure SMTP
|
Configure SMTP
|
||||||
<Header.Subheader>To support the system email sending</Header.Subheader>
|
<Header.Subheader>
|
||||||
|
To support the system email sending
|
||||||
|
</Header.Subheader>
|
||||||
</Header>
|
</Header>
|
||||||
<Form.Group widths={3}>
|
<Form.Group widths={3}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
@ -318,7 +320,10 @@ const SystemSetting = () => {
|
|||||||
Configure Discord OAuth App
|
Configure Discord OAuth App
|
||||||
<Header.Subheader>
|
<Header.Subheader>
|
||||||
To support login & registration via GitHub,
|
To support login & registration via GitHub,
|
||||||
<a href='https://discord.com/developers/applications' target='_blank'>
|
<a
|
||||||
|
href='https://discord.com/developers/applications'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
Click here
|
Click here
|
||||||
</a>
|
</a>
|
||||||
Manage your Discord OAuth App
|
Manage your Discord OAuth App
|
||||||
@ -441,7 +446,8 @@ const SystemSetting = () => {
|
|||||||
<a href='https://dash.cloudflare.com/' target='_blank'>
|
<a href='https://dash.cloudflare.com/' target='_blank'>
|
||||||
Click here
|
Click here
|
||||||
</a>
|
</a>
|
||||||
Manage your Turnstile Sites, recommend selecting Invisible Widget Type
|
Manage your Turnstile Sites, recommend selecting Invisible Widget
|
||||||
|
Type
|
||||||
</Header.Subheader>
|
</Header.Subheader>
|
||||||
</Header>
|
</Header>
|
||||||
<Form.Group widths={3}>
|
<Form.Group widths={3}>
|
||||||
|
@ -1,31 +1,66 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Label, Modal, Pagination, Popup, Table } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Label,
|
||||||
|
Modal,
|
||||||
|
Pagination,
|
||||||
|
Popup,
|
||||||
|
Table,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { API, copy, showError, showSuccess, showWarning, timestamp2string } from '../helpers';
|
import {
|
||||||
|
API,
|
||||||
|
copy,
|
||||||
|
showError,
|
||||||
|
showSuccess,
|
||||||
|
showWarning,
|
||||||
|
timestamp2string,
|
||||||
|
} from '../helpers';
|
||||||
|
|
||||||
import { ITEMS_PER_PAGE } from '../constants';
|
import { ITEMS_PER_PAGE } from '../constants';
|
||||||
import { renderQuota } from '../helpers/render';
|
import { renderQuota } from '../helpers/render';
|
||||||
|
|
||||||
function renderTimestamp(timestamp) {
|
function renderTimestamp(timestamp) {
|
||||||
return (
|
return <>{timestamp2string(timestamp)}</>;
|
||||||
<>
|
|
||||||
{timestamp2string(timestamp)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStatus(status) {
|
function renderStatus(status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 1:
|
case 1:
|
||||||
return <Label basic color='green'>已启用</Label>;
|
return (
|
||||||
|
<Label basic color='green'>
|
||||||
|
已启用
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 2:
|
case 2:
|
||||||
return <Label basic color='red'> 已禁用 </Label>;
|
return (
|
||||||
|
<Label basic color='red'>
|
||||||
|
{' '}
|
||||||
|
已禁用{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 3:
|
case 3:
|
||||||
return <Label basic color='yellow'> 已过期 </Label>;
|
return (
|
||||||
|
<Label basic color='yellow'>
|
||||||
|
{' '}
|
||||||
|
已过期{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
case 4:
|
case 4:
|
||||||
return <Label basic color='grey'> 已耗尽 </Label>;
|
return (
|
||||||
|
<Label basic color='grey'>
|
||||||
|
{' '}
|
||||||
|
已耗尽{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return <Label basic color='black'> 未知状态 </Label>;
|
return (
|
||||||
|
<Label basic color='black'>
|
||||||
|
{' '}
|
||||||
|
未知状态{' '}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +103,7 @@ const TokensTable = () => {
|
|||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await loadTokens(activePage - 1);
|
await loadTokens(activePage - 1);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadTokens(0)
|
loadTokens(0)
|
||||||
@ -221,7 +256,7 @@ const TokensTable = () => {
|
|||||||
{tokens
|
{tokens
|
||||||
.slice(
|
.slice(
|
||||||
(activePage - 1) * ITEMS_PER_PAGE,
|
(activePage - 1) * ITEMS_PER_PAGE,
|
||||||
activePage * ITEMS_PER_PAGE
|
activePage * ITEMS_PER_PAGE,
|
||||||
)
|
)
|
||||||
.map((token, idx) => {
|
.map((token, idx) => {
|
||||||
if (token.deleted) return <></>;
|
if (token.deleted) return <></>;
|
||||||
@ -230,20 +265,30 @@ const TokensTable = () => {
|
|||||||
<Table.Cell>{token.name ? token.name : '无'}</Table.Cell>
|
<Table.Cell>{token.name ? token.name : '无'}</Table.Cell>
|
||||||
<Table.Cell>{renderStatus(token.status)}</Table.Cell>
|
<Table.Cell>{renderStatus(token.status)}</Table.Cell>
|
||||||
<Table.Cell>{renderQuota(token.used_quota)}</Table.Cell>
|
<Table.Cell>{renderQuota(token.used_quota)}</Table.Cell>
|
||||||
<Table.Cell>{token.unlimited_quota ? '无限制' : renderQuota(token.remain_quota, 2)}</Table.Cell>
|
<Table.Cell>
|
||||||
|
{token.unlimited_quota
|
||||||
|
? '无限制'
|
||||||
|
: renderQuota(token.remain_quota, 2)}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>{renderTimestamp(token.created_time)}</Table.Cell>
|
<Table.Cell>{renderTimestamp(token.created_time)}</Table.Cell>
|
||||||
<Table.Cell>{token.expired_time === -1 ? '永不过期' : renderTimestamp(token.expired_time)}</Table.Cell>
|
<Table.Cell>
|
||||||
|
{token.expired_time === -1
|
||||||
|
? '永不过期'
|
||||||
|
: renderTimestamp(token.expired_time)}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
size={'small'}
|
size={'small'}
|
||||||
positive
|
positive
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
let key = "sk-" + token.key;
|
let key = 'sk-' + token.key;
|
||||||
if (await copy(key)) {
|
if (await copy(key)) {
|
||||||
showSuccess('已复制到剪贴板!');
|
showSuccess('已复制到剪贴板!');
|
||||||
} else {
|
} else {
|
||||||
showWarning('无法复制到剪贴板,请手动复制,已将令牌填入搜索框。');
|
showWarning(
|
||||||
|
'无法复制到剪贴板,请手动复制,已将令牌填入搜索框。',
|
||||||
|
);
|
||||||
setSearchKeyword(key);
|
setSearchKeyword(key);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -275,7 +320,7 @@ const TokensTable = () => {
|
|||||||
manageToken(
|
manageToken(
|
||||||
token.id,
|
token.id,
|
||||||
token.status === 1 ? 'disable' : 'enable',
|
token.status === 1 ? 'disable' : 'enable',
|
||||||
idx
|
idx,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -301,7 +346,9 @@ const TokensTable = () => {
|
|||||||
<Button size='small' as={Link} to='/token/add' loading={loading}>
|
<Button size='small' as={Link} to='/token/add' loading={loading}>
|
||||||
添加新的令牌
|
添加新的令牌
|
||||||
</Button>
|
</Button>
|
||||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
<Button size='small' onClick={refresh} loading={loading}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
<Pagination
|
<Pagination
|
||||||
floated='right'
|
floated='right'
|
||||||
activePage={activePage}
|
activePage={activePage}
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Label, Pagination, Popup, Table } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Label,
|
||||||
|
Pagination,
|
||||||
|
Popup,
|
||||||
|
Table,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { API, showError, showSuccess } from '../helpers';
|
import { API, showError, showSuccess } from '../helpers';
|
||||||
|
|
||||||
import { ITEMS_PER_PAGE } from '../constants';
|
import { ITEMS_PER_PAGE } from '../constants';
|
||||||
import { renderGroup, renderNumber, renderQuota, renderText } from '../helpers/render';
|
import {
|
||||||
|
renderGroup,
|
||||||
|
renderNumber,
|
||||||
|
renderQuota,
|
||||||
|
renderText,
|
||||||
|
} from '../helpers/render';
|
||||||
|
|
||||||
function renderRole(role) {
|
function renderRole(role) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
@ -65,7 +77,7 @@ const UsersTable = () => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
const res = await API.post('/api/user/manage', {
|
const res = await API.post('/api/user/manage', {
|
||||||
username,
|
username,
|
||||||
action
|
action,
|
||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -215,7 +227,7 @@ const UsersTable = () => {
|
|||||||
{users
|
{users
|
||||||
.slice(
|
.slice(
|
||||||
(activePage - 1) * ITEMS_PER_PAGE,
|
(activePage - 1) * ITEMS_PER_PAGE,
|
||||||
activePage * ITEMS_PER_PAGE
|
activePage * ITEMS_PER_PAGE,
|
||||||
)
|
)
|
||||||
.map((user, idx) => {
|
.map((user, idx) => {
|
||||||
if (user.deleted) return <></>;
|
if (user.deleted) return <></>;
|
||||||
@ -226,7 +238,9 @@ const UsersTable = () => {
|
|||||||
<Popup
|
<Popup
|
||||||
content={user.email ? user.email : '未绑定邮箱地址'}
|
content={user.email ? user.email : '未绑定邮箱地址'}
|
||||||
key={user.username}
|
key={user.username}
|
||||||
header={user.display_name ? user.display_name : user.username}
|
header={
|
||||||
|
user.display_name ? user.display_name : user.username
|
||||||
|
}
|
||||||
trigger={<span>{renderText(user.username, 10)}</span>}
|
trigger={<span>{renderText(user.username, 10)}</span>}
|
||||||
hoverable
|
hoverable
|
||||||
/>
|
/>
|
||||||
@ -236,9 +250,22 @@ const UsersTable = () => {
|
|||||||
{/* {user.email ? <Popup hoverable content={user.email} trigger={<span>{renderText(user.email, 24)}</span>} /> : '无'}*/}
|
{/* {user.email ? <Popup hoverable content={user.email} trigger={<span>{renderText(user.email, 24)}</span>} /> : '无'}*/}
|
||||||
{/*</Table.Cell>*/}
|
{/*</Table.Cell>*/}
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Popup content='剩余额度' trigger={<Label basic>{renderQuota(user.quota)}</Label>} />
|
<Popup
|
||||||
<Popup content='已用额度' trigger={<Label basic>{renderQuota(user.used_quota)}</Label>} />
|
content='剩余额度'
|
||||||
<Popup content='请求次数' trigger={<Label basic>{renderNumber(user.request_count)}</Label>} />
|
trigger={<Label basic>{renderQuota(user.quota)}</Label>}
|
||||||
|
/>
|
||||||
|
<Popup
|
||||||
|
content='已用额度'
|
||||||
|
trigger={
|
||||||
|
<Label basic>{renderQuota(user.used_quota)}</Label>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Popup
|
||||||
|
content='请求次数'
|
||||||
|
trigger={
|
||||||
|
<Label basic>{renderNumber(user.request_count)}</Label>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>{renderRole(user.role)}</Table.Cell>
|
<Table.Cell>{renderRole(user.role)}</Table.Cell>
|
||||||
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
|
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
|
||||||
@ -266,7 +293,11 @@ const UsersTable = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Popup
|
<Popup
|
||||||
trigger={
|
trigger={
|
||||||
<Button size='small' negative disabled={user.role === 100}>
|
<Button
|
||||||
|
size='small'
|
||||||
|
negative
|
||||||
|
disabled={user.role === 100}
|
||||||
|
>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@ -289,7 +320,7 @@ const UsersTable = () => {
|
|||||||
manageUser(
|
manageUser(
|
||||||
user.username,
|
user.username,
|
||||||
user.status === 1 ? 'disable' : 'enable',
|
user.status === 1 ? 'disable' : 'enable',
|
||||||
idx
|
idx,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
disabled={user.role === 100}
|
disabled={user.role === 100}
|
||||||
|
@ -12,6 +12,6 @@ export const CHANNEL_OPTIONS = [
|
|||||||
{ key: 12, text: 'API2GPT', value: 12, color: 'blue' },
|
{ key: 12, text: 'API2GPT', value: 12, color: 'blue' },
|
||||||
{ key: 13, text: 'AIGC2D', value: 13, color: 'purple' },
|
{ key: 13, text: 'AIGC2D', value: 13, color: 'purple' },
|
||||||
|
|
||||||
//
|
//
|
||||||
{ key: 14, text: 'Chanzhaoyu/chatgpt-web', value: 14, color: 'purple' },
|
{ key: 14, text: 'Chanzhaoyu/chatgpt-web', value: 14, color: 'purple' },
|
||||||
];
|
];
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export * from './toast.constants';
|
export * from './toast.constants';
|
||||||
export * from './user.constants';
|
export * from './user.constants';
|
||||||
export * from './common.constant';
|
export * from './common.constant';
|
||||||
export * from './channel.constants';
|
export * from './channel.constants';
|
||||||
|
@ -3,5 +3,5 @@ export const toastConstants = {
|
|||||||
INFO_TIMEOUT: 3000,
|
INFO_TIMEOUT: 3000,
|
||||||
ERROR_TIMEOUT: 5000,
|
ERROR_TIMEOUT: 5000,
|
||||||
WARNING_TIMEOUT: 10000,
|
WARNING_TIMEOUT: 10000,
|
||||||
NOTICE_TIMEOUT: 20000
|
NOTICE_TIMEOUT: 20000,
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
export const userConstants = {
|
export const userConstants = {
|
||||||
REGISTER_REQUEST: 'USERS_REGISTER_REQUEST',
|
REGISTER_REQUEST: 'USERS_REGISTER_REQUEST',
|
||||||
REGISTER_SUCCESS: 'USERS_REGISTER_SUCCESS',
|
REGISTER_SUCCESS: 'USERS_REGISTER_SUCCESS',
|
||||||
REGISTER_FAILURE: 'USERS_REGISTER_FAILURE',
|
REGISTER_FAILURE: 'USERS_REGISTER_FAILURE',
|
||||||
|
|
||||||
LOGIN_REQUEST: 'USERS_LOGIN_REQUEST',
|
LOGIN_REQUEST: 'USERS_LOGIN_REQUEST',
|
||||||
LOGIN_SUCCESS: 'USERS_LOGIN_SUCCESS',
|
LOGIN_SUCCESS: 'USERS_LOGIN_SUCCESS',
|
||||||
LOGIN_FAILURE: 'USERS_LOGIN_FAILURE',
|
LOGIN_FAILURE: 'USERS_LOGIN_FAILURE',
|
||||||
|
|
||||||
LOGOUT: 'USERS_LOGOUT',
|
|
||||||
|
|
||||||
GETALL_REQUEST: 'USERS_GETALL_REQUEST',
|
LOGOUT: 'USERS_LOGOUT',
|
||||||
GETALL_SUCCESS: 'USERS_GETALL_SUCCESS',
|
|
||||||
GETALL_FAILURE: 'USERS_GETALL_FAILURE',
|
|
||||||
|
|
||||||
DELETE_REQUEST: 'USERS_DELETE_REQUEST',
|
GETALL_REQUEST: 'USERS_GETALL_REQUEST',
|
||||||
DELETE_SUCCESS: 'USERS_DELETE_SUCCESS',
|
GETALL_SUCCESS: 'USERS_GETALL_SUCCESS',
|
||||||
DELETE_FAILURE: 'USERS_DELETE_FAILURE'
|
GETALL_FAILURE: 'USERS_GETALL_FAILURE',
|
||||||
|
|
||||||
|
DELETE_REQUEST: 'USERS_DELETE_REQUEST',
|
||||||
|
DELETE_SUCCESS: 'USERS_DELETE_SUCCESS',
|
||||||
|
DELETE_FAILURE: 'USERS_DELETE_FAILURE',
|
||||||
};
|
};
|
||||||
|
@ -16,4 +16,4 @@ export const StatusProvider = ({ children }) => {
|
|||||||
{children}
|
{children}
|
||||||
</StatusContext.Provider>
|
</StatusContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
// contexts/User/index.jsx
|
// contexts/User/index.jsx
|
||||||
|
|
||||||
import React from "react"
|
import React from 'react';
|
||||||
import { reducer, initialState } from "./reducer"
|
import { reducer, initialState } from './reducer';
|
||||||
|
|
||||||
export const UserContext = React.createContext({
|
export const UserContext = React.createContext({
|
||||||
state: initialState,
|
state: initialState,
|
||||||
dispatch: () => null
|
dispatch: () => null,
|
||||||
})
|
});
|
||||||
|
|
||||||
export const UserProvider = ({ children }) => {
|
export const UserProvider = ({ children }) => {
|
||||||
const [state, dispatch] = React.useReducer(reducer, initialState)
|
const [state, dispatch] = React.useReducer(reducer, initialState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserContext.Provider value={[ state, dispatch ]}>
|
<UserContext.Provider value={[state, dispatch]}>
|
||||||
{ children }
|
{children}
|
||||||
</UserContext.Provider>
|
</UserContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
@ -3,12 +3,12 @@ export const reducer = (state, action) => {
|
|||||||
case 'login':
|
case 'login':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
user: action.payload
|
user: action.payload,
|
||||||
};
|
};
|
||||||
case 'logout':
|
case 'logout':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
user: undefined
|
user: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -17,5 +17,5 @@ export const reducer = (state, action) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const initialState = {
|
export const initialState = {
|
||||||
user: undefined
|
user: undefined,
|
||||||
};
|
};
|
||||||
|
@ -9,5 +9,5 @@ API.interceptors.response.use(
|
|||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
showError(error);
|
showError(error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
export function authHeader() {
|
export function authHeader() {
|
||||||
// return authorization header with jwt token
|
// return authorization header with jwt token
|
||||||
let user = JSON.parse(localStorage.getItem('user'));
|
let user = JSON.parse(localStorage.getItem('user'));
|
||||||
|
|
||||||
if (user && user.token) {
|
if (user && user.token) {
|
||||||
return { 'Authorization': 'Bearer ' + user.token };
|
return { Authorization: 'Bearer ' + user.token };
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
|
|
||||||
export const history = createBrowserHistory();
|
export const history = createBrowserHistory();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export * from './history';
|
export * from './history';
|
||||||
export * from './auth-header';
|
export * from './auth-header';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './api';
|
export * from './api';
|
||||||
|
@ -13,16 +13,18 @@ export function renderGroup(group) {
|
|||||||
}
|
}
|
||||||
let groups = group.split(',');
|
let groups = group.split(',');
|
||||||
groups.sort();
|
groups.sort();
|
||||||
return <>
|
return (
|
||||||
{groups.map((group) => {
|
<>
|
||||||
if (group === 'vip' || group === 'pro') {
|
{groups.map((group) => {
|
||||||
return <Label color='yellow'>{group}</Label>;
|
if (group === 'vip' || group === 'pro') {
|
||||||
} else if (group === 'svip' || group === 'premium') {
|
return <Label color='yellow'>{group}</Label>;
|
||||||
return <Label color='red'>{group}</Label>;
|
} else if (group === 'svip' || group === 'premium') {
|
||||||
}
|
return <Label color='red'>{group}</Label>;
|
||||||
return <Label>{group}</Label>;
|
}
|
||||||
})}
|
return <Label>{group}</Label>;
|
||||||
</>;
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderNumber(num) {
|
export function renderNumber(num) {
|
||||||
@ -55,4 +57,4 @@ export function renderQuotaWithPrompt(quota, digits) {
|
|||||||
return `(等价金额:${renderQuota(quota, digits)})`;
|
return `(等价金额:${renderQuota(quota, digits)})`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ export function getSystemName() {
|
|||||||
export function getLogo() {
|
export function getLogo() {
|
||||||
let logo = localStorage.getItem('logo');
|
let logo = localStorage.getItem('logo');
|
||||||
if (!logo) return '/logo.png';
|
if (!logo) return '/logo.png';
|
||||||
return logo
|
return logo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFooterHTML() {
|
export function getFooterHTML() {
|
||||||
@ -147,17 +147,7 @@ export function timestamp2string(timestamp) {
|
|||||||
second = '0' + second;
|
second = '0' + second;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
year +
|
year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second
|
||||||
'-' +
|
|
||||||
month +
|
|
||||||
'-' +
|
|
||||||
day +
|
|
||||||
' ' +
|
|
||||||
hour +
|
|
||||||
':' +
|
|
||||||
minute +
|
|
||||||
':' +
|
|
||||||
second
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,4 +167,4 @@ export const verifyJSON = (str) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1,35 +1,37 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 55px;
|
padding-top: 55px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, "Microsoft YaHei", sans-serif;
|
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, 'Microsoft YaHei',
|
||||||
-webkit-font-smoothing: antialiased;
|
sans-serif;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-webkit-font-smoothing: antialiased;
|
||||||
scrollbar-width: none;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
body::-webkit-scrollbar {
|
body::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small-icon .icon {
|
.small-icon .icon {
|
||||||
font-size: 1em !important;
|
font-size: 1em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-footer {
|
.custom-footer {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
.hide-on-mobile {
|
.hide-on-mobile {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,5 +27,5 @@ root.render(
|
|||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
</StatusProvider>
|
</StatusProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
@ -31,8 +31,8 @@ const About = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{aboutLoaded && about === '' ? (
|
||||||
aboutLoaded && about === '' ? <>
|
<>
|
||||||
<Segment>
|
<Segment>
|
||||||
<Header as='h3'>关于</Header>
|
<Header as='h3'>关于</Header>
|
||||||
<p>可在设置页面设置关于内容,支持 HTML & Markdown</p>
|
<p>可在设置页面设置关于内容,支持 HTML & Markdown</p>
|
||||||
@ -41,20 +41,26 @@ const About = () => {
|
|||||||
https://github.com/songquanpeng/one-api
|
https://github.com/songquanpeng/one-api
|
||||||
</a>
|
</a>
|
||||||
</Segment>
|
</Segment>
|
||||||
</> : <>
|
</>
|
||||||
{
|
) : (
|
||||||
about.startsWith('https://') ? <iframe
|
<>
|
||||||
|
{about.startsWith('https://') ? (
|
||||||
|
<iframe
|
||||||
src={about}
|
src={about}
|
||||||
style={{ width: '100%', height: '100vh', border: 'none' }}
|
style={{ width: '100%', height: '100vh', border: 'none' }}
|
||||||
/> : <Segment>
|
/>
|
||||||
<div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: about }}></div>
|
) : (
|
||||||
|
<Segment>
|
||||||
|
<div
|
||||||
|
style={{ fontSize: 'larger' }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: about }}
|
||||||
|
></div>
|
||||||
</Segment>
|
</Segment>
|
||||||
}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default About;
|
export default About;
|
||||||
|
@ -1,13 +1,26 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Header, Input, Message, Segment } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Header,
|
||||||
|
Input,
|
||||||
|
Message,
|
||||||
|
Segment,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { API, showError, showInfo, showSuccess, verifyJSON } from '../../helpers';
|
import {
|
||||||
|
API,
|
||||||
|
showError,
|
||||||
|
showInfo,
|
||||||
|
showSuccess,
|
||||||
|
verifyJSON,
|
||||||
|
} from '../../helpers';
|
||||||
import { CHANNEL_OPTIONS } from '../../constants';
|
import { CHANNEL_OPTIONS } from '../../constants';
|
||||||
|
|
||||||
const MODEL_MAPPING_EXAMPLE = {
|
const MODEL_MAPPING_EXAMPLE = {
|
||||||
'gpt-3.5-turbo-0301': 'gpt-3.5-turbo',
|
'gpt-3.5-turbo-0301': 'gpt-3.5-turbo',
|
||||||
'gpt-4-0314': 'gpt-4',
|
'gpt-4-0314': 'gpt-4',
|
||||||
'gpt-4-32k-0314': 'gpt-4-32k'
|
'gpt-4-32k-0314': 'gpt-4-32k',
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditChannel = () => {
|
const EditChannel = () => {
|
||||||
@ -34,7 +47,7 @@ const EditChannel = () => {
|
|||||||
const [fullModels, setFullModels] = useState([]);
|
const [fullModels, setFullModels] = useState([]);
|
||||||
const [customModel, setCustomModel] = useState('');
|
const [customModel, setCustomModel] = useState('');
|
||||||
const handleInputChange = (e, { name, value }) => {
|
const handleInputChange = (e, { name, value }) => {
|
||||||
console.log(name, value)
|
console.log(name, value);
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,7 +66,7 @@ const EditChannel = () => {
|
|||||||
localModelOptions.push({
|
localModelOptions.push({
|
||||||
key: model,
|
key: model,
|
||||||
text: model,
|
text: model,
|
||||||
value: model
|
value: model,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -66,7 +79,11 @@ const EditChannel = () => {
|
|||||||
data.groups = data.group.split(',');
|
data.groups = data.group.split(',');
|
||||||
}
|
}
|
||||||
if (data.model_mapping !== '') {
|
if (data.model_mapping !== '') {
|
||||||
data.model_mapping = JSON.stringify(JSON.parse(data.model_mapping), null, 2);
|
data.model_mapping = JSON.stringify(
|
||||||
|
JSON.parse(data.model_mapping),
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setInputs(data);
|
setInputs(data);
|
||||||
} else {
|
} else {
|
||||||
@ -78,13 +95,19 @@ const EditChannel = () => {
|
|||||||
const fetchModels = async () => {
|
const fetchModels = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await API.get(`/api/channel/models`);
|
let res = await API.get(`/api/channel/models`);
|
||||||
setModelOptions(res.data.data.map((model) => ({
|
setModelOptions(
|
||||||
key: model.id,
|
res.data.data.map((model) => ({
|
||||||
text: model.id,
|
key: model.id,
|
||||||
value: model.id
|
text: model.id,
|
||||||
})));
|
value: model.id,
|
||||||
|
})),
|
||||||
|
);
|
||||||
setFullModels(res.data.data.map((model) => model.id));
|
setFullModels(res.data.data.map((model) => model.id));
|
||||||
setBasicModels(res.data.data.filter((model) => !model.id.startsWith('gpt-4')).map((model) => model.id));
|
setBasicModels(
|
||||||
|
res.data.data
|
||||||
|
.filter((model) => !model.id.startsWith('gpt-4'))
|
||||||
|
.map((model) => model.id),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
}
|
}
|
||||||
@ -93,11 +116,13 @@ const EditChannel = () => {
|
|||||||
const fetchGroups = async () => {
|
const fetchGroups = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await API.get(`/api/group/`);
|
let res = await API.get(`/api/group/`);
|
||||||
setGroupOptions(res.data.data.map((group) => ({
|
setGroupOptions(
|
||||||
key: group,
|
res.data.data.map((group) => ({
|
||||||
text: group,
|
key: group,
|
||||||
value: group
|
text: group,
|
||||||
})));
|
value: group,
|
||||||
|
})),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
}
|
}
|
||||||
@ -126,7 +151,10 @@ const EditChannel = () => {
|
|||||||
}
|
}
|
||||||
let localInputs = inputs;
|
let localInputs = inputs;
|
||||||
if (localInputs.base_url.endsWith('/')) {
|
if (localInputs.base_url.endsWith('/')) {
|
||||||
localInputs.base_url = localInputs.base_url.slice(0, localInputs.base_url.length - 1);
|
localInputs.base_url = localInputs.base_url.slice(
|
||||||
|
0,
|
||||||
|
localInputs.base_url.length - 1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (localInputs.type === 3 && localInputs.other === '') {
|
if (localInputs.type === 3 && localInputs.other === '') {
|
||||||
localInputs.other = '2023-03-15-preview';
|
localInputs.other = '2023-03-15-preview';
|
||||||
@ -135,7 +163,10 @@ const EditChannel = () => {
|
|||||||
localInputs.models = localInputs.models.join(',');
|
localInputs.models = localInputs.models.join(',');
|
||||||
localInputs.group = localInputs.groups.join(',');
|
localInputs.group = localInputs.groups.join(',');
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
res = await API.put(`/api/channel/`, { ...localInputs, id: parseInt(channelId) });
|
res = await API.put(`/api/channel/`, {
|
||||||
|
...localInputs,
|
||||||
|
id: parseInt(channelId),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
res = await API.post(`/api/channel/`, localInputs);
|
res = await API.post(`/api/channel/`, localInputs);
|
||||||
}
|
}
|
||||||
@ -167,65 +198,74 @@ const EditChannel = () => {
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
{
|
{inputs.type === 3 && (
|
||||||
inputs.type === 3 && (
|
<>
|
||||||
<>
|
<Message>
|
||||||
<Message>
|
注意,<strong>模型部署名称必须和模型名称保持一致</strong>,因为
|
||||||
注意,<strong>模型部署名称必须和模型名称保持一致</strong>,因为 One API 会把请求体中的 model
|
One API 会把请求体中的 model
|
||||||
参数替换为你的部署名称(模型名称中的点会被剔除),<a target='_blank'
|
参数替换为你的部署名称(模型名称中的点会被剔除),
|
||||||
href='https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271'>图片演示</a>。
|
<a
|
||||||
</Message>
|
target='_blank'
|
||||||
<Form.Field>
|
href='https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271'
|
||||||
<Form.Input
|
>
|
||||||
label='AZURE_OPENAI_ENDPOINT'
|
图片演示
|
||||||
name='base_url'
|
</a>
|
||||||
placeholder={'请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com'}
|
。
|
||||||
onChange={handleInputChange}
|
</Message>
|
||||||
value={inputs.base_url}
|
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
|
||||||
</Form.Field>
|
|
||||||
<Form.Field>
|
|
||||||
<Form.Input
|
|
||||||
label='默认 API 版本'
|
|
||||||
name='other'
|
|
||||||
placeholder={'请输入默认 API 版本,例如:2023-03-15-preview,该配置可以被实际的请求查询参数所覆盖'}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
value={inputs.other}
|
|
||||||
autoComplete='new-password'
|
|
||||||
/>
|
|
||||||
</Form.Field>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
inputs.type === 8 && (
|
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='Base URL'
|
label='AZURE_OPENAI_ENDPOINT'
|
||||||
name='base_url'
|
name='base_url'
|
||||||
placeholder={'请输入自定义渠道的 Base URL,例如:https://openai.justsong.cn'}
|
placeholder={
|
||||||
|
'请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com'
|
||||||
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={inputs.base_url}
|
value={inputs.base_url}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
inputs.type !== 3 && inputs.type !== 8 && (
|
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='镜像'
|
label='默认 API 版本'
|
||||||
name='base_url'
|
name='other'
|
||||||
placeholder={'此项可选,输入镜像站地址,格式为:https://domain.com'}
|
placeholder={
|
||||||
|
'请输入默认 API 版本,例如:2023-03-15-preview,该配置可以被实际的请求查询参数所覆盖'
|
||||||
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={inputs.base_url}
|
value={inputs.other}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
)
|
</>
|
||||||
}
|
)}
|
||||||
|
{inputs.type === 8 && (
|
||||||
|
<Form.Field>
|
||||||
|
<Form.Input
|
||||||
|
label='Base URL'
|
||||||
|
name='base_url'
|
||||||
|
placeholder={
|
||||||
|
'请输入自定义渠道的 Base URL,例如:https://openai.justsong.cn'
|
||||||
|
}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={inputs.base_url}
|
||||||
|
autoComplete='new-password'
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
)}
|
||||||
|
{inputs.type !== 3 && inputs.type !== 8 && (
|
||||||
|
<Form.Field>
|
||||||
|
<Form.Input
|
||||||
|
label='镜像'
|
||||||
|
name='base_url'
|
||||||
|
placeholder={
|
||||||
|
'此项可选,输入镜像站地址,格式为:https://domain.com'
|
||||||
|
}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={inputs.base_url}
|
||||||
|
autoComplete='new-password'
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
)}
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='名称'
|
label='名称'
|
||||||
@ -270,29 +310,52 @@ const EditChannel = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<div style={{ lineHeight: '40px', marginBottom: '12px' }}>
|
<div style={{ lineHeight: '40px', marginBottom: '12px' }}>
|
||||||
<Button type={'button'} onClick={() => {
|
<Button
|
||||||
handleInputChange(null, { name: 'models', value: basicModels });
|
type={'button'}
|
||||||
}}>填入基础模型</Button>
|
onClick={() => {
|
||||||
<Button type={'button'} onClick={() => {
|
handleInputChange(null, { name: 'models', value: basicModels });
|
||||||
handleInputChange(null, { name: 'models', value: fullModels });
|
}}
|
||||||
}}>填入所有模型</Button>
|
>
|
||||||
<Button type={'button'} onClick={() => {
|
填入基础模型
|
||||||
handleInputChange(null, { name: 'models', value: [] });
|
</Button>
|
||||||
}}>清除所有模型</Button>
|
<Button
|
||||||
|
type={'button'}
|
||||||
|
onClick={() => {
|
||||||
|
handleInputChange(null, { name: 'models', value: fullModels });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
填入所有模型
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={'button'}
|
||||||
|
onClick={() => {
|
||||||
|
handleInputChange(null, { name: 'models', value: [] });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
清除所有模型
|
||||||
|
</Button>
|
||||||
<Input
|
<Input
|
||||||
action={
|
action={
|
||||||
<Button type={'button'} onClick={()=>{
|
<Button
|
||||||
let localModels = [...inputs.models];
|
type={'button'}
|
||||||
localModels.push(customModel);
|
onClick={() => {
|
||||||
let localModelOptions = [...modelOptions];
|
let localModels = [...inputs.models];
|
||||||
localModelOptions.push({
|
localModels.push(customModel);
|
||||||
key: customModel,
|
let localModelOptions = [...modelOptions];
|
||||||
text: customModel,
|
localModelOptions.push({
|
||||||
value: customModel,
|
key: customModel,
|
||||||
});
|
text: customModel,
|
||||||
setModelOptions(localModelOptions);
|
value: customModel,
|
||||||
handleInputChange(null, { name: 'models', value: localModels });
|
});
|
||||||
}}>填入</Button>
|
setModelOptions(localModelOptions);
|
||||||
|
handleInputChange(null, {
|
||||||
|
name: 'models',
|
||||||
|
value: localModels,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
填入
|
||||||
|
</Button>
|
||||||
}
|
}
|
||||||
placeholder='输入自定义模型名称'
|
placeholder='输入自定义模型名称'
|
||||||
value={customModel}
|
value={customModel}
|
||||||
@ -315,7 +378,11 @@ const EditChannel = () => {
|
|||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
label='模型映射'
|
label='模型映射'
|
||||||
placeholder={`此项可选,为一个 JSON 文本,键为用户请求的模型名称,值为要替换的模型名称,例如:\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`}
|
placeholder={`此项可选,为一个 JSON 文本,键为用户请求的模型名称,值为要替换的模型名称,例如:\n${JSON.stringify(
|
||||||
|
MODEL_MAPPING_EXAMPLE,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)}`}
|
||||||
name='model_mapping'
|
name='model_mapping'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={inputs.model_mapping}
|
value={inputs.model_mapping}
|
||||||
@ -323,18 +390,23 @@ const EditChannel = () => {
|
|||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
{
|
{batch ? (
|
||||||
batch ? <Form.Field>
|
<Form.Field>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
label='密钥'
|
label='密钥'
|
||||||
name='key'
|
name='key'
|
||||||
placeholder={'请输入密钥,一行一个'}
|
placeholder={'请输入密钥,一行一个'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={inputs.key}
|
value={inputs.key}
|
||||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
style={{
|
||||||
|
minHeight: 150,
|
||||||
|
fontFamily: 'JetBrains Mono, Consolas',
|
||||||
|
}}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field> : <Form.Field>
|
</Form.Field>
|
||||||
|
) : (
|
||||||
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='密钥'
|
label='密钥'
|
||||||
name='key'
|
name='key'
|
||||||
@ -345,18 +417,18 @@ const EditChannel = () => {
|
|||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
}
|
)}
|
||||||
{
|
{!isEdit && (
|
||||||
!isEdit && (
|
<Form.Checkbox
|
||||||
<Form.Checkbox
|
checked={batch}
|
||||||
checked={batch}
|
label='批量创建'
|
||||||
label='批量创建'
|
name='batch'
|
||||||
name='batch'
|
onChange={() => setBatch(!batch)}
|
||||||
onChange={() => setBatch(!batch)}
|
/>
|
||||||
/>
|
)}
|
||||||
)
|
<Button type={isEdit ? 'button' : 'submit'} positive onClick={submit}>
|
||||||
}
|
提交
|
||||||
<Button type={isEdit ? "button" : "submit"} positive onClick={submit}>提交</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Segment>
|
</Segment>
|
||||||
</>
|
</>
|
||||||
|
@ -11,5 +11,4 @@ const Chat = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Chat;
|
export default Chat;
|
||||||
|
@ -52,8 +52,8 @@ const Home = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{homePageContentLoaded && homePageContent === '' ? (
|
||||||
homePageContentLoaded && homePageContent === '' ? <>
|
<>
|
||||||
<Segment>
|
<Segment>
|
||||||
<Header as='h3'>系统状况</Header>
|
<Header as='h3'>系统状况</Header>
|
||||||
<Grid columns={2} stackable>
|
<Grid columns={2} stackable>
|
||||||
@ -121,16 +121,22 @@ const Home = () => {
|
|||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Segment>
|
</Segment>
|
||||||
</> : <>
|
</>
|
||||||
{
|
) : (
|
||||||
homePageContent.startsWith('https://') ? <iframe
|
<>
|
||||||
|
{homePageContent.startsWith('https://') ? (
|
||||||
|
<iframe
|
||||||
src={homePageContent}
|
src={homePageContent}
|
||||||
style={{ width: '100%', height: '100vh', border: 'none' }}
|
style={{ width: '100%', height: '100vh', border: 'none' }}
|
||||||
/> : <div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: homePageContent }}></div>
|
/>
|
||||||
}
|
) : (
|
||||||
|
<div
|
||||||
|
style={{ fontSize: 'larger' }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: homePageContent }}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,15 +5,13 @@ const NotFound = () => (
|
|||||||
<>
|
<>
|
||||||
<Header
|
<Header
|
||||||
block
|
block
|
||||||
as="h4"
|
as='h4'
|
||||||
content="404"
|
content='404'
|
||||||
attached="top"
|
attached='top'
|
||||||
icon="info"
|
icon='info'
|
||||||
className="small-icon"
|
className='small-icon'
|
||||||
/>
|
/>
|
||||||
<Segment attached="bottom">
|
<Segment attached='bottom'>未找到所请求的页面</Segment>
|
||||||
未找到所请求的页面
|
|
||||||
</Segment>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ const EditRedemption = () => {
|
|||||||
const originInputs = {
|
const originInputs = {
|
||||||
name: '',
|
name: '',
|
||||||
quota: 100000,
|
quota: 100000,
|
||||||
count: 1
|
count: 1,
|
||||||
};
|
};
|
||||||
const [inputs, setInputs] = useState(originInputs);
|
const [inputs, setInputs] = useState(originInputs);
|
||||||
const { name, quota, count } = inputs;
|
const { name, quota, count } = inputs;
|
||||||
@ -44,10 +44,13 @@ const EditRedemption = () => {
|
|||||||
localInputs.quota = parseInt(localInputs.quota);
|
localInputs.quota = parseInt(localInputs.quota);
|
||||||
let res;
|
let res;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
res = await API.put(`/api/redemption/`, { ...localInputs, id: parseInt(redemptionId) });
|
res = await API.put(`/api/redemption/`, {
|
||||||
|
...localInputs,
|
||||||
|
id: parseInt(redemptionId),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
res = await API.post(`/api/redemption/`, {
|
res = await API.post(`/api/redemption/`, {
|
||||||
...localInputs
|
...localInputs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
@ -62,9 +65,9 @@ const EditRedemption = () => {
|
|||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
if (!isEdit && data) {
|
if (!isEdit && data) {
|
||||||
let text = "";
|
let text = '';
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
text += data[i] + "\n";
|
text += data[i] + '\n';
|
||||||
}
|
}
|
||||||
downloadTextAsFile(text, `${inputs.name}.txt`);
|
downloadTextAsFile(text, `${inputs.name}.txt`);
|
||||||
}
|
}
|
||||||
@ -97,8 +100,8 @@ const EditRedemption = () => {
|
|||||||
type='number'
|
type='number'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
{
|
{!isEdit && (
|
||||||
!isEdit && <>
|
<>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='生成数量'
|
label='生成数量'
|
||||||
@ -111,8 +114,10 @@ const EditRedemption = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
<Button positive onClick={submit}>提交</Button>
|
<Button positive onClick={submit}>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Segment>
|
</Segment>
|
||||||
</>
|
</>
|
||||||
|
@ -6,7 +6,7 @@ const Redemption = () => (
|
|||||||
<>
|
<>
|
||||||
<Segment>
|
<Segment>
|
||||||
<Header as='h3'>管理兑换码</Header>
|
<Header as='h3'>管理兑换码</Header>
|
||||||
<RedemptionsTable/>
|
<RedemptionsTable />
|
||||||
</Segment>
|
</Segment>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -14,8 +14,8 @@ const Setting = () => {
|
|||||||
<Tab.Pane attached={false}>
|
<Tab.Pane attached={false}>
|
||||||
<PersonalSetting />
|
<PersonalSetting />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
)
|
),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isRoot()) {
|
if (isRoot()) {
|
||||||
@ -25,7 +25,7 @@ const Setting = () => {
|
|||||||
<Tab.Pane attached={false}>
|
<Tab.Pane attached={false}>
|
||||||
<OperationSetting />
|
<OperationSetting />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
)
|
),
|
||||||
});
|
});
|
||||||
panes.push({
|
panes.push({
|
||||||
menuItem: '系统设置',
|
menuItem: '系统设置',
|
||||||
@ -33,7 +33,7 @@ const Setting = () => {
|
|||||||
<Tab.Pane attached={false}>
|
<Tab.Pane attached={false}>
|
||||||
<SystemSetting />
|
<SystemSetting />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
)
|
),
|
||||||
});
|
});
|
||||||
panes.push({
|
panes.push({
|
||||||
menuItem: '其他设置',
|
menuItem: '其他设置',
|
||||||
@ -41,7 +41,7 @@ const Setting = () => {
|
|||||||
<Tab.Pane attached={false}>
|
<Tab.Pane attached={false}>
|
||||||
<OtherSetting />
|
<OtherSetting />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
)
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,15 @@ const EditToken = () => {
|
|||||||
remain_quota: isEdit ? 0 : 500000,
|
remain_quota: isEdit ? 0 : 500000,
|
||||||
expired_time: -1,
|
expired_time: -1,
|
||||||
unlimited_quota: false,
|
unlimited_quota: false,
|
||||||
models: isEdit ? [] : ['gpt-3.5-turbo', 'gpt-3.5-turbo-0301', 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo-16k', 'gpt-3.5-turbo-16k-0613']
|
models: isEdit
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
'gpt-3.5-turbo',
|
||||||
|
'gpt-3.5-turbo-0301',
|
||||||
|
'gpt-3.5-turbo-0613',
|
||||||
|
'gpt-3.5-turbo-16k',
|
||||||
|
'gpt-3.5-turbo-16k-0613',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
const [modelOptions, setModelOptions] = useState([]);
|
const [modelOptions, setModelOptions] = useState([]);
|
||||||
const [basicModels, setBasicModels] = useState([]);
|
const [basicModels, setBasicModels] = useState([]);
|
||||||
@ -48,13 +56,19 @@ const EditToken = () => {
|
|||||||
const fetchModels = async () => {
|
const fetchModels = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await API.get(`/api/channel/models`);
|
let res = await API.get(`/api/channel/models`);
|
||||||
setModelOptions(res.data.data.map((model) => ({
|
setModelOptions(
|
||||||
key: model.id,
|
res.data.data.map((model) => ({
|
||||||
text: model.id,
|
key: model.id,
|
||||||
value: model.id
|
text: model.id,
|
||||||
})));
|
value: model.id,
|
||||||
|
})),
|
||||||
|
);
|
||||||
setFullModels(res.data.data.map((model) => model.id));
|
setFullModels(res.data.data.map((model) => model.id));
|
||||||
setBasicModels(res.data.data.filter((model) => !model.id.startsWith('gpt-4')).map((model) => model.id));
|
setBasicModels(
|
||||||
|
res.data.data
|
||||||
|
.filter((model) => !model.id.startsWith('gpt-4'))
|
||||||
|
.map((model) => model.id),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
}
|
}
|
||||||
@ -103,7 +117,10 @@ const EditToken = () => {
|
|||||||
localInputs.models = localInputs.models.join(',');
|
localInputs.models = localInputs.models.join(',');
|
||||||
let res;
|
let res;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
res = await API.put(`/api/token/`, { ...localInputs, id: parseInt(tokenId) });
|
res = await API.put(`/api/token/`, {
|
||||||
|
...localInputs,
|
||||||
|
id: parseInt(tokenId),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
res = await API.post(`/api/token/`, localInputs);
|
res = await API.post(`/api/token/`, localInputs);
|
||||||
}
|
}
|
||||||
@ -144,7 +161,9 @@ const EditToken = () => {
|
|||||||
<Form.Input
|
<Form.Input
|
||||||
label='过期时间'
|
label='过期时间'
|
||||||
name='expired_time'
|
name='expired_time'
|
||||||
placeholder={'请输入过期时间,格式为 yyyy-MM-dd HH:mm:ss,-1 表示无限制'}
|
placeholder={
|
||||||
|
'请输入过期时间,格式为 yyyy-MM-dd HH:mm:ss,-1 表示无限制'
|
||||||
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={expired_time}
|
value={expired_time}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
@ -152,23 +171,50 @@ const EditToken = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<div style={{ lineHeight: '40px' }}>
|
<div style={{ lineHeight: '40px' }}>
|
||||||
<Button type={'button'} onClick={() => {
|
<Button
|
||||||
setExpiredTime(0, 0, 0, 0);
|
type={'button'}
|
||||||
}}>永不过期</Button>
|
onClick={() => {
|
||||||
<Button type={'button'} onClick={() => {
|
setExpiredTime(0, 0, 0, 0);
|
||||||
setExpiredTime(1, 0, 0, 0);
|
}}
|
||||||
}}>一个月后过期</Button>
|
>
|
||||||
<Button type={'button'} onClick={() => {
|
永不过期
|
||||||
setExpiredTime(0, 1, 0, 0);
|
</Button>
|
||||||
}}>一天后过期</Button>
|
<Button
|
||||||
<Button type={'button'} onClick={() => {
|
type={'button'}
|
||||||
setExpiredTime(0, 0, 1, 0);
|
onClick={() => {
|
||||||
}}>一小时后过期</Button>
|
setExpiredTime(1, 0, 0, 0);
|
||||||
<Button type={'button'} onClick={() => {
|
}}
|
||||||
setExpiredTime(0, 0, 0, 1);
|
>
|
||||||
}}>一分钟后过期</Button>
|
一个月后过期
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={'button'}
|
||||||
|
onClick={() => {
|
||||||
|
setExpiredTime(0, 1, 0, 0);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
一天后过期
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={'button'}
|
||||||
|
onClick={() => {
|
||||||
|
setExpiredTime(0, 0, 1, 0);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
一小时后过期
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={'button'}
|
||||||
|
onClick={() => {
|
||||||
|
setExpiredTime(0, 0, 0, 1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
一分钟后过期
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Message>注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。</Message>
|
<Message>
|
||||||
|
注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。
|
||||||
|
</Message>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={`额度${renderQuotaWithPrompt(remain_quota)}`}
|
label={`额度${renderQuotaWithPrompt(remain_quota)}`}
|
||||||
@ -181,9 +227,14 @@ const EditToken = () => {
|
|||||||
disabled={unlimited_quota}
|
disabled={unlimited_quota}
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Button type={'button'} onClick={() => {
|
<Button
|
||||||
setUnlimitedQuota();
|
type={'button'}
|
||||||
}}>{unlimited_quota ? '取消无限额度' : '设置为无限额度'}</Button>
|
onClick={() => {
|
||||||
|
setUnlimitedQuota();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{unlimited_quota ? '取消无限额度' : '设置为无限额度'}
|
||||||
|
</Button>
|
||||||
<Form.Field style={{ marginTop: '12px' }}>
|
<Form.Field style={{ marginTop: '12px' }}>
|
||||||
<Form.Dropdown
|
<Form.Dropdown
|
||||||
label='模型'
|
label='模型'
|
||||||
@ -200,17 +251,34 @@ const EditToken = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<div style={{ lineHeight: '40px', marginBottom: '12px' }}>
|
<div style={{ lineHeight: '40px', marginBottom: '12px' }}>
|
||||||
<Button type={'button'} onClick={() => {
|
<Button
|
||||||
handleInputChange(null, { name: 'models', value: basicModels });
|
type={'button'}
|
||||||
}}>填入基础模型</Button>
|
onClick={() => {
|
||||||
<Button type={'button'} onClick={() => {
|
handleInputChange(null, { name: 'models', value: basicModels });
|
||||||
handleInputChange(null, { name: 'models', value: fullModels });
|
}}
|
||||||
}}>填入所有模型</Button>
|
>
|
||||||
<Button type={'button'} onClick={() => {
|
填入基础模型
|
||||||
handleInputChange(null, { name: 'models', value: [] });
|
</Button>
|
||||||
}}>清除所有模型</Button>
|
<Button
|
||||||
|
type={'button'}
|
||||||
|
onClick={() => {
|
||||||
|
handleInputChange(null, { name: 'models', value: fullModels });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
填入所有模型
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={'button'}
|
||||||
|
onClick={() => {
|
||||||
|
handleInputChange(null, { name: 'models', value: [] });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
清除所有模型
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button positive onClick={submit}>提交</Button>
|
<Button positive onClick={submit}>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Segment>
|
</Segment>
|
||||||
</>
|
</>
|
||||||
|
@ -6,7 +6,7 @@ const Token = () => (
|
|||||||
<>
|
<>
|
||||||
<Segment>
|
<Segment>
|
||||||
<Header as='h3'>我的令牌</Header>
|
<Header as='h3'>我的令牌</Header>
|
||||||
<TokensTable/>
|
<TokensTable />
|
||||||
</Segment>
|
</Segment>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Grid, Header, Segment, Statistic } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Grid,
|
||||||
|
Header,
|
||||||
|
Segment,
|
||||||
|
Statistic,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { API, showError, showInfo, showSuccess } from '../../helpers';
|
import { API, showError, showInfo, showSuccess } from '../../helpers';
|
||||||
import { renderQuota } from '../../helpers/render';
|
import { renderQuota } from '../../helpers/render';
|
||||||
|
|
||||||
@ -10,11 +17,11 @@ const TopUp = () => {
|
|||||||
|
|
||||||
const topUp = async () => {
|
const topUp = async () => {
|
||||||
if (redemptionCode === '') {
|
if (redemptionCode === '') {
|
||||||
showInfo('请输入充值码!')
|
showInfo('请输入充值码!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await API.post('/api/user/topup', {
|
const res = await API.post('/api/user/topup', {
|
||||||
key: redemptionCode
|
key: redemptionCode,
|
||||||
});
|
});
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -36,15 +43,15 @@ const TopUp = () => {
|
|||||||
window.open(topUpLink, '_blank');
|
window.open(topUpLink, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserQuota = async ()=>{
|
const getUserQuota = async () => {
|
||||||
let res = await API.get(`/api/user/self`);
|
let res = await API.get(`/api/user/self`);
|
||||||
const {success, message, data} = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setUserQuota(data.quota);
|
setUserQuota(data.quota);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let status = localStorage.getItem('status');
|
let status = localStorage.getItem('status');
|
||||||
@ -92,5 +99,4 @@ const TopUp = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default TopUp;
|
export default TopUp;
|
||||||
|
@ -30,38 +30,38 @@ const AddUser = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Segment>
|
<Segment>
|
||||||
<Header as="h3">创建新用户账户</Header>
|
<Header as='h3'>创建新用户账户</Header>
|
||||||
<Form autoComplete="off">
|
<Form autoComplete='off'>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label="用户名"
|
label='用户名'
|
||||||
name="username"
|
name='username'
|
||||||
placeholder={'请输入用户名'}
|
placeholder={'请输入用户名'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={username}
|
value={username}
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label="显示名称"
|
label='显示名称'
|
||||||
name="display_name"
|
name='display_name'
|
||||||
placeholder={'请输入显示名称'}
|
placeholder={'请输入显示名称'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={display_name}
|
value={display_name}
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label="密码"
|
label='密码'
|
||||||
name="password"
|
name='password'
|
||||||
type={'password'}
|
type={'password'}
|
||||||
placeholder={'请输入密码'}
|
placeholder={'请输入密码'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={password}
|
value={password}
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
@ -17,22 +17,32 @@ const EditUser = () => {
|
|||||||
wechat_id: '',
|
wechat_id: '',
|
||||||
email: '',
|
email: '',
|
||||||
quota: 0,
|
quota: 0,
|
||||||
group: 'default'
|
group: 'default',
|
||||||
});
|
});
|
||||||
const [groupOptions, setGroupOptions] = useState([]);
|
const [groupOptions, setGroupOptions] = useState([]);
|
||||||
const { username, display_name, password, github_id, wechat_id, email, quota, discord_id } =
|
const {
|
||||||
inputs;
|
username,
|
||||||
|
display_name,
|
||||||
|
password,
|
||||||
|
github_id,
|
||||||
|
wechat_id,
|
||||||
|
email,
|
||||||
|
quota,
|
||||||
|
discord_id,
|
||||||
|
} = inputs;
|
||||||
const handleInputChange = (e, { name, value }) => {
|
const handleInputChange = (e, { name, value }) => {
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
};
|
};
|
||||||
const fetchGroups = async () => {
|
const fetchGroups = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await API.get(`/api/group/`);
|
let res = await API.get(`/api/group/`);
|
||||||
setGroupOptions(res.data.data.map((group) => ({
|
setGroupOptions(
|
||||||
key: group,
|
res.data.data.map((group) => ({
|
||||||
text: group,
|
key: group,
|
||||||
value: group,
|
text: group,
|
||||||
})));
|
value: group,
|
||||||
|
})),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
}
|
}
|
||||||
@ -116,8 +126,8 @@ const EditUser = () => {
|
|||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
{
|
{userId && (
|
||||||
userId && <>
|
<>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Dropdown
|
<Form.Dropdown
|
||||||
label='分组'
|
label='分组'
|
||||||
@ -146,7 +156,7 @@ const EditUser = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='已绑定的 GitHub 账户'
|
label='已绑定的 GitHub 账户'
|
||||||
@ -187,7 +197,9 @@ const EditUser = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Button positive onClick={submit}>提交</Button>
|
<Button positive onClick={submit}>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Segment>
|
</Segment>
|
||||||
</>
|
</>
|
||||||
|
@ -6,7 +6,7 @@ const User = () => (
|
|||||||
<>
|
<>
|
||||||
<Segment>
|
<Segment>
|
||||||
<Header as='h3'>管理用户</Header>
|
<Header as='h3'>管理用户</Header>
|
||||||
<UsersTable/>
|
<UsersTable />
|
||||||
</Segment>
|
</Segment>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user