Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
a6cc0226
authored
Jun 20, 2023
by
JustSong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: able to display quota in dollar
parent
b220e8e8
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
126 additions
and
45 deletions
+126
-45
common/constants.go
+2
-0
common/logger.go
+8
-0
controller/billing.go
+22
-6
controller/misc.go
+17
-15
controller/relay-text.go
+1
-1
controller/user.go
+1
-1
model/cache.go
+2
-2
model/option.go
+6
-0
model/redemption.go
+1
-1
model/user.go
+8
-3
web/src/App.js
+2
-0
web/src/components/OperationSetting.js
+31
-8
web/src/components/RedemptionsTable.js
+2
-1
web/src/components/TokensTable.js
+2
-1
web/src/components/UsersTable.js
+3
-3
web/src/helpers/render.js
+12
-0
web/src/pages/Redemption/EditRedemption.js
+2
-1
web/src/pages/Token/EditToken.js
+2
-1
web/src/pages/TopUp/index.js
+2
-1
No files found.
common/constants.go
View file @
a6cc0226
...
@@ -15,6 +15,8 @@ var Footer = ""
...
@@ -15,6 +15,8 @@ var Footer = ""
var
Logo
=
""
var
Logo
=
""
var
TopUpLink
=
""
var
TopUpLink
=
""
var
ChatLink
=
""
var
ChatLink
=
""
var
QuotaPerUnit
=
500
*
1000.0
// $0.002 / 1K tokens
var
DisplayInCurrencyEnabled
=
false
var
UsingSQLite
=
false
var
UsingSQLite
=
false
...
...
common/logger.go
View file @
a6cc0226
...
@@ -42,3 +42,11 @@ func FatalLog(v ...any) {
...
@@ -42,3 +42,11 @@ func FatalLog(v ...any) {
_
,
_
=
fmt
.
Fprintf
(
gin
.
DefaultErrorWriter
,
"[FATAL] %v | %v
\n
"
,
t
.
Format
(
"2006/01/02 - 15:04:05"
),
v
)
_
,
_
=
fmt
.
Fprintf
(
gin
.
DefaultErrorWriter
,
"[FATAL] %v | %v
\n
"
,
t
.
Format
(
"2006/01/02 - 15:04:05"
),
v
)
os
.
Exit
(
1
)
os
.
Exit
(
1
)
}
}
func
LogQuota
(
quota
int
)
string
{
if
DisplayInCurrencyEnabled
{
return
fmt
.
Sprintf
(
"$%.6f 额度"
,
float64
(
quota
)
/
QuotaPerUnit
)
}
else
{
return
fmt
.
Sprintf
(
"%d 点额度"
,
quota
)
}
}
controller/billing.go
View file @
a6cc0226
...
@@ -2,6 +2,7 @@ package controller
...
@@ -2,6 +2,7 @@ package controller
import
(
import
(
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
"one-api/common"
"one-api/model"
"one-api/model"
)
)
...
@@ -18,23 +19,38 @@ func GetSubscription(c *gin.Context) {
...
@@ -18,23 +19,38 @@ func GetSubscription(c *gin.Context) {
})
})
return
return
}
}
amount
:=
float64
(
quota
)
if
common
.
DisplayInCurrencyEnabled
{
amount
/=
common
.
QuotaPerUnit
}
subscription
:=
OpenAISubscriptionResponse
{
subscription
:=
OpenAISubscriptionResponse
{
Object
:
"billing_subscription"
,
Object
:
"billing_subscription"
,
HasPaymentMethod
:
true
,
HasPaymentMethod
:
true
,
SoftLimitUSD
:
float64
(
quota
)
,
SoftLimitUSD
:
amount
,
HardLimitUSD
:
float64
(
quota
)
,
HardLimitUSD
:
amount
,
SystemHardLimitUSD
:
float64
(
quota
)
,
SystemHardLimitUSD
:
amount
,
}
}
c
.
JSON
(
200
,
subscription
)
c
.
JSON
(
200
,
subscription
)
return
return
}
}
func
GetUsage
(
c
*
gin
.
Context
)
{
func
GetUsage
(
c
*
gin
.
Context
)
{
//userId := c.GetInt("id")
userId
:=
c
.
GetInt
(
"id"
)
// TODO: get usage from database
quota
,
err
:=
model
.
GetUserUsedQuota
(
userId
)
if
err
!=
nil
{
openAIError
:=
OpenAIError
{
Message
:
err
.
Error
(),
Type
:
"one_api_error"
,
}
c
.
JSON
(
200
,
gin
.
H
{
"error"
:
openAIError
,
})
return
}
amount
:=
float64
(
quota
)
usage
:=
OpenAIUsageResponse
{
usage
:=
OpenAIUsageResponse
{
Object
:
"list"
,
Object
:
"list"
,
TotalUsage
:
0
,
TotalUsage
:
amount
,
}
}
c
.
JSON
(
200
,
usage
)
c
.
JSON
(
200
,
usage
)
return
return
...
...
controller/misc.go
View file @
a6cc0226
...
@@ -14,21 +14,23 @@ func GetStatus(c *gin.Context) {
...
@@ -14,21 +14,23 @@ func GetStatus(c *gin.Context) {
"success"
:
true
,
"success"
:
true
,
"message"
:
""
,
"message"
:
""
,
"data"
:
gin
.
H
{
"data"
:
gin
.
H
{
"version"
:
common
.
Version
,
"version"
:
common
.
Version
,
"start_time"
:
common
.
StartTime
,
"start_time"
:
common
.
StartTime
,
"email_verification"
:
common
.
EmailVerificationEnabled
,
"email_verification"
:
common
.
EmailVerificationEnabled
,
"github_oauth"
:
common
.
GitHubOAuthEnabled
,
"github_oauth"
:
common
.
GitHubOAuthEnabled
,
"github_client_id"
:
common
.
GitHubClientId
,
"github_client_id"
:
common
.
GitHubClientId
,
"system_name"
:
common
.
SystemName
,
"system_name"
:
common
.
SystemName
,
"logo"
:
common
.
Logo
,
"logo"
:
common
.
Logo
,
"footer_html"
:
common
.
Footer
,
"footer_html"
:
common
.
Footer
,
"wechat_qrcode"
:
common
.
WeChatAccountQRCodeImageURL
,
"wechat_qrcode"
:
common
.
WeChatAccountQRCodeImageURL
,
"wechat_login"
:
common
.
WeChatAuthEnabled
,
"wechat_login"
:
common
.
WeChatAuthEnabled
,
"server_address"
:
common
.
ServerAddress
,
"server_address"
:
common
.
ServerAddress
,
"turnstile_check"
:
common
.
TurnstileCheckEnabled
,
"turnstile_check"
:
common
.
TurnstileCheckEnabled
,
"turnstile_site_key"
:
common
.
TurnstileSiteKey
,
"turnstile_site_key"
:
common
.
TurnstileSiteKey
,
"top_up_link"
:
common
.
TopUpLink
,
"top_up_link"
:
common
.
TopUpLink
,
"chat_link"
:
common
.
ChatLink
,
"chat_link"
:
common
.
ChatLink
,
"quota_per_unit"
:
common
.
QuotaPerUnit
,
"display_in_currency"
:
common
.
DisplayInCurrencyEnabled
,
},
},
})
})
return
return
...
...
controller/relay-text.go
View file @
a6cc0226
...
@@ -138,7 +138,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
...
@@ -138,7 +138,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
}
}
tokenName
:=
c
.
GetString
(
"token_name"
)
tokenName
:=
c
.
GetString
(
"token_name"
)
userId
:=
c
.
GetInt
(
"id"
)
userId
:=
c
.
GetInt
(
"id"
)
model
.
RecordLog
(
userId
,
model
.
LogTypeConsume
,
fmt
.
Sprintf
(
"通过令牌「%s」使用模型 %s 消耗 %
d 点额度(模型倍率 %.2f,分组倍率 %.2f)"
,
tokenName
,
textRequest
.
Model
,
quota
,
modelRatio
,
groupRatio
))
model
.
RecordLog
(
userId
,
model
.
LogTypeConsume
,
fmt
.
Sprintf
(
"通过令牌「%s」使用模型 %s 消耗 %
s(模型倍率 %.2f,分组倍率 %.2f)"
,
tokenName
,
textRequest
.
Model
,
common
.
LogQuota
(
quota
)
,
modelRatio
,
groupRatio
))
model
.
UpdateUserUsedQuotaAndRequestCount
(
userId
,
quota
)
model
.
UpdateUserUsedQuotaAndRequestCount
(
userId
,
quota
)
channelId
:=
c
.
GetInt
(
"channel_id"
)
channelId
:=
c
.
GetInt
(
"channel_id"
)
model
.
UpdateChannelUsedQuota
(
channelId
,
quota
)
model
.
UpdateChannelUsedQuota
(
channelId
,
quota
)
...
...
controller/user.go
View file @
a6cc0226
...
@@ -384,7 +384,7 @@ func UpdateUser(c *gin.Context) {
...
@@ -384,7 +384,7 @@ func UpdateUser(c *gin.Context) {
return
return
}
}
if
originUser
.
Quota
!=
updatedUser
.
Quota
{
if
originUser
.
Quota
!=
updatedUser
.
Quota
{
model
.
RecordLog
(
originUser
.
Id
,
model
.
LogTypeManage
,
fmt
.
Sprintf
(
"管理员将用户额度从 %
d 点修改为 %d 点"
,
originUser
.
Quota
,
updatedUser
.
Quota
))
model
.
RecordLog
(
originUser
.
Id
,
model
.
LogTypeManage
,
fmt
.
Sprintf
(
"管理员将用户额度从 %
s修改为 %s"
,
common
.
LogQuota
(
originUser
.
Quota
),
common
.
LogQuota
(
updatedUser
.
Quota
)
))
}
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"success"
:
true
,
...
...
model/cache.go
View file @
a6cc0226
...
@@ -16,12 +16,12 @@ const (
...
@@ -16,12 +16,12 @@ const (
func
CacheGetTokenByKey
(
key
string
)
(
*
Token
,
error
)
{
func
CacheGetTokenByKey
(
key
string
)
(
*
Token
,
error
)
{
var
token
Token
var
token
Token
if
!
common
.
RedisEnabled
{
if
!
common
.
RedisEnabled
{
err
:=
DB
.
Where
(
"`key` = ?"
,
key
)
.
First
(
token
)
.
Error
err
:=
DB
.
Where
(
"`key` = ?"
,
key
)
.
First
(
&
token
)
.
Error
return
&
token
,
err
return
&
token
,
err
}
}
tokenObjectString
,
err
:=
common
.
RedisGet
(
fmt
.
Sprintf
(
"token:%s"
,
key
))
tokenObjectString
,
err
:=
common
.
RedisGet
(
fmt
.
Sprintf
(
"token:%s"
,
key
))
if
err
!=
nil
{
if
err
!=
nil
{
err
:=
DB
.
Where
(
"`key` = ?"
,
key
)
.
First
(
token
)
.
Error
err
:=
DB
.
Where
(
"`key` = ?"
,
key
)
.
First
(
&
token
)
.
Error
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
...
model/option.go
View file @
a6cc0226
...
@@ -35,6 +35,7 @@ func InitOptionMap() {
...
@@ -35,6 +35,7 @@ func InitOptionMap() {
common
.
OptionMap
[
"RegisterEnabled"
]
=
strconv
.
FormatBool
(
common
.
RegisterEnabled
)
common
.
OptionMap
[
"RegisterEnabled"
]
=
strconv
.
FormatBool
(
common
.
RegisterEnabled
)
common
.
OptionMap
[
"AutomaticDisableChannelEnabled"
]
=
strconv
.
FormatBool
(
common
.
AutomaticDisableChannelEnabled
)
common
.
OptionMap
[
"AutomaticDisableChannelEnabled"
]
=
strconv
.
FormatBool
(
common
.
AutomaticDisableChannelEnabled
)
common
.
OptionMap
[
"LogConsumeEnabled"
]
=
strconv
.
FormatBool
(
common
.
LogConsumeEnabled
)
common
.
OptionMap
[
"LogConsumeEnabled"
]
=
strconv
.
FormatBool
(
common
.
LogConsumeEnabled
)
common
.
OptionMap
[
"DisplayInCurrencyEnabled"
]
=
strconv
.
FormatBool
(
common
.
DisplayInCurrencyEnabled
)
common
.
OptionMap
[
"ChannelDisableThreshold"
]
=
strconv
.
FormatFloat
(
common
.
ChannelDisableThreshold
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"ChannelDisableThreshold"
]
=
strconv
.
FormatFloat
(
common
.
ChannelDisableThreshold
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"SMTPServer"
]
=
""
common
.
OptionMap
[
"SMTPServer"
]
=
""
common
.
OptionMap
[
"SMTPFrom"
]
=
""
common
.
OptionMap
[
"SMTPFrom"
]
=
""
...
@@ -64,6 +65,7 @@ func InitOptionMap() {
...
@@ -64,6 +65,7 @@ func InitOptionMap() {
common
.
OptionMap
[
"GroupRatio"
]
=
common
.
GroupRatio2JSONString
()
common
.
OptionMap
[
"GroupRatio"
]
=
common
.
GroupRatio2JSONString
()
common
.
OptionMap
[
"TopUpLink"
]
=
common
.
TopUpLink
common
.
OptionMap
[
"TopUpLink"
]
=
common
.
TopUpLink
common
.
OptionMap
[
"ChatLink"
]
=
common
.
ChatLink
common
.
OptionMap
[
"ChatLink"
]
=
common
.
ChatLink
common
.
OptionMap
[
"QuotaPerUnit"
]
=
strconv
.
FormatFloat
(
common
.
QuotaPerUnit
,
'f'
,
-
1
,
64
)
common
.
OptionMapRWMutex
.
Unlock
()
common
.
OptionMapRWMutex
.
Unlock
()
loadOptionsFromDatabase
()
loadOptionsFromDatabase
()
}
}
...
@@ -140,6 +142,8 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -140,6 +142,8 @@ func updateOptionMap(key string, value string) (err error) {
common
.
AutomaticDisableChannelEnabled
=
boolValue
common
.
AutomaticDisableChannelEnabled
=
boolValue
case
"LogConsumeEnabled"
:
case
"LogConsumeEnabled"
:
common
.
LogConsumeEnabled
=
boolValue
common
.
LogConsumeEnabled
=
boolValue
case
"DisplayInCurrencyEnabled"
:
common
.
DisplayInCurrencyEnabled
=
boolValue
}
}
}
}
switch
key
{
switch
key
{
...
@@ -196,6 +200,8 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -196,6 +200,8 @@ func updateOptionMap(key string, value string) (err error) {
common
.
ChatLink
=
value
common
.
ChatLink
=
value
case
"ChannelDisableThreshold"
:
case
"ChannelDisableThreshold"
:
common
.
ChannelDisableThreshold
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
common
.
ChannelDisableThreshold
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
case
"QuotaPerUnit"
:
common
.
QuotaPerUnit
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
}
}
return
err
return
err
}
}
model/redemption.go
View file @
a6cc0226
...
@@ -66,7 +66,7 @@ func Redeem(key string, userId int) (quota int, err error) {
...
@@ -66,7 +66,7 @@ func Redeem(key string, userId int) (quota int, err error) {
if
err
!=
nil
{
if
err
!=
nil
{
common
.
SysError
(
"更新兑换码状态失败:"
+
err
.
Error
())
common
.
SysError
(
"更新兑换码状态失败:"
+
err
.
Error
())
}
}
RecordLog
(
userId
,
LogTypeTopup
,
fmt
.
Sprintf
(
"通过兑换码充值 %
d 点额度"
,
redemption
.
Quota
))
RecordLog
(
userId
,
LogTypeTopup
,
fmt
.
Sprintf
(
"通过兑换码充值 %
s"
,
common
.
LogQuota
(
redemption
.
Quota
)
))
}()
}()
return
redemption
.
Quota
,
nil
return
redemption
.
Quota
,
nil
}
}
...
...
model/user.go
View file @
a6cc0226
...
@@ -93,16 +93,16 @@ func (user *User) Insert(inviterId int) error {
...
@@ -93,16 +93,16 @@ func (user *User) Insert(inviterId int) error {
return
result
.
Error
return
result
.
Error
}
}
if
common
.
QuotaForNewUser
>
0
{
if
common
.
QuotaForNewUser
>
0
{
RecordLog
(
user
.
Id
,
LogTypeSystem
,
fmt
.
Sprintf
(
"新用户注册赠送 %
d 点额度"
,
common
.
QuotaForNewUser
))
RecordLog
(
user
.
Id
,
LogTypeSystem
,
fmt
.
Sprintf
(
"新用户注册赠送 %
s"
,
common
.
LogQuota
(
common
.
QuotaForNewUser
)
))
}
}
if
inviterId
!=
0
{
if
inviterId
!=
0
{
if
common
.
QuotaForInvitee
>
0
{
if
common
.
QuotaForInvitee
>
0
{
_
=
IncreaseUserQuota
(
user
.
Id
,
common
.
QuotaForInvitee
)
_
=
IncreaseUserQuota
(
user
.
Id
,
common
.
QuotaForInvitee
)
RecordLog
(
user
.
Id
,
LogTypeSystem
,
fmt
.
Sprintf
(
"使用邀请码赠送 %
d 点额度"
,
common
.
QuotaForInvitee
))
RecordLog
(
user
.
Id
,
LogTypeSystem
,
fmt
.
Sprintf
(
"使用邀请码赠送 %
s"
,
common
.
LogQuota
(
common
.
QuotaForInvitee
)
))
}
}
if
common
.
QuotaForInviter
>
0
{
if
common
.
QuotaForInviter
>
0
{
_
=
IncreaseUserQuota
(
inviterId
,
common
.
QuotaForInviter
)
_
=
IncreaseUserQuota
(
inviterId
,
common
.
QuotaForInviter
)
RecordLog
(
inviterId
,
LogTypeSystem
,
fmt
.
Sprintf
(
"邀请用户赠送 %
d 点额度"
,
common
.
QuotaForInviter
))
RecordLog
(
inviterId
,
LogTypeSystem
,
fmt
.
Sprintf
(
"邀请用户赠送 %
s"
,
common
.
LogQuota
(
common
.
QuotaForInviter
)
))
}
}
}
}
return
nil
return
nil
...
@@ -256,6 +256,11 @@ func GetUserQuota(id int) (quota int, err error) {
...
@@ -256,6 +256,11 @@ func GetUserQuota(id int) (quota int, err error) {
return
quota
,
err
return
quota
,
err
}
}
func
GetUserUsedQuota
(
id
int
)
(
quota
int
,
err
error
)
{
err
=
DB
.
Model
(
&
User
{})
.
Where
(
"id = ?"
,
id
)
.
Select
(
"used_quota"
)
.
Find
(
&
quota
)
.
Error
return
quota
,
err
}
func
GetUserEmail
(
id
int
)
(
email
string
,
err
error
)
{
func
GetUserEmail
(
id
int
)
(
email
string
,
err
error
)
{
err
=
DB
.
Model
(
&
User
{})
.
Where
(
"id = ?"
,
id
)
.
Select
(
"email"
)
.
Find
(
&
email
)
.
Error
err
=
DB
.
Model
(
&
User
{})
.
Where
(
"id = ?"
,
id
)
.
Select
(
"email"
)
.
Find
(
&
email
)
.
Error
return
email
,
err
return
email
,
err
...
...
web/src/App.js
View file @
a6cc0226
...
@@ -48,6 +48,8 @@ function App() {
...
@@ -48,6 +48,8 @@ function App() {
localStorage
.
setItem
(
'system_name'
,
data
.
system_name
);
localStorage
.
setItem
(
'system_name'
,
data
.
system_name
);
localStorage
.
setItem
(
'logo'
,
data
.
logo
);
localStorage
.
setItem
(
'logo'
,
data
.
logo
);
localStorage
.
setItem
(
'footer_html'
,
data
.
footer_html
);
localStorage
.
setItem
(
'footer_html'
,
data
.
footer_html
);
localStorage
.
setItem
(
'quota_per_unit'
,
data
.
quota_per_unit
);
localStorage
.
setItem
(
'display_in_currency'
,
data
.
display_in_currency
);
if
(
data
.
chat_link
)
{
if
(
data
.
chat_link
)
{
localStorage
.
setItem
(
'chat_link'
,
data
.
chat_link
);
localStorage
.
setItem
(
'chat_link'
,
data
.
chat_link
);
}
else
{
}
else
{
...
...
web/src/components/OperationSetting.js
View file @
a6cc0226
...
@@ -13,9 +13,11 @@ const OperationSetting = () => {
...
@@ -13,9 +13,11 @@ const OperationSetting = () => {
GroupRatio
:
''
,
GroupRatio
:
''
,
TopUpLink
:
''
,
TopUpLink
:
''
,
ChatLink
:
''
,
ChatLink
:
''
,
QuotaPerUnit
:
0
,
AutomaticDisableChannelEnabled
:
''
,
AutomaticDisableChannelEnabled
:
''
,
ChannelDisableThreshold
:
0
,
ChannelDisableThreshold
:
0
,
LogConsumeEnabled
:
''
LogConsumeEnabled
:
''
,
DisplayInCurrencyEnabled
:
''
});
});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
let
[
loading
,
setLoading
]
=
useState
(
false
);
let
[
loading
,
setLoading
]
=
useState
(
false
);
...
@@ -118,6 +120,9 @@ const OperationSetting = () => {
...
@@ -118,6 +120,9 @@ const OperationSetting = () => {
if
(
originInputs
[
'ChatLink'
]
!==
inputs
.
ChatLink
)
{
if
(
originInputs
[
'ChatLink'
]
!==
inputs
.
ChatLink
)
{
await
updateOption
(
'ChatLink'
,
inputs
.
ChatLink
);
await
updateOption
(
'ChatLink'
,
inputs
.
ChatLink
);
}
}
if
(
originInputs
[
'QuotaPerUnit'
]
!==
inputs
.
QuotaPerUnit
)
{
await
updateOption
(
'QuotaPerUnit'
,
inputs
.
QuotaPerUnit
);
}
break
;
break
;
}
}
};
};
...
@@ -129,7 +134,7 @@ const OperationSetting = () => {
...
@@ -129,7 +134,7 @@ const OperationSetting = () => {
<
Header
as
=
'h3'
>
<
Header
as
=
'h3'
>
通用设置
通用设置
<
/Header
>
<
/Header
>
<
Form
.
Group
widths
=
{
2
}
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Input
<
Form
.
Input
label
=
'充值链接'
label
=
'充值链接'
name
=
'TopUpLink'
name
=
'TopUpLink'
...
@@ -148,6 +153,30 @@ const OperationSetting = () => {
...
@@ -148,6 +153,30 @@ const OperationSetting = () => {
type
=
'link'
type
=
'link'
placeholder
=
'例如 ChatGPT Next Web 的部署地址'
placeholder
=
'例如 ChatGPT Next Web 的部署地址'
/>
/>
<
Form
.
Input
label
=
'额度汇率'
name
=
'QuotaPerUnit'
onChange
=
{
handleInputChange
}
autoComplete
=
'new-password'
value
=
{
inputs
.
QuotaPerUnit
}
type
=
'number'
step
=
'0.01'
placeholder
=
'一单位货币能兑换的额度'
/>
<
/Form.Group
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
checked
=
{
inputs
.
LogConsumeEnabled
===
'true'
}
label
=
'启用额度消费日志记录'
name
=
'LogConsumeEnabled'
onChange
=
{
handleInputChange
}
/
>
<
Form
.
Checkbox
checked
=
{
inputs
.
DisplayInCurrencyEnabled
===
'true'
}
label
=
'以货币形式显示额度'
name
=
'DisplayInCurrencyEnabled'
onChange
=
{
handleInputChange
}
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{()
=>
{
<
Form
.
Button
onClick
=
{()
=>
{
submitConfig
(
'general'
).
then
();
submitConfig
(
'general'
).
then
();
...
@@ -264,12 +293,6 @@ const OperationSetting = () => {
...
@@ -264,12 +293,6 @@ const OperationSetting = () => {
placeholder
=
'为一个 JSON 文本,键为分组名称,值为倍率'
placeholder
=
'为一个 JSON 文本,键为分组名称,值为倍率'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Checkbox
checked
=
{
inputs
.
LogConsumeEnabled
===
'true'
}
label
=
'启用额度消费日志记录'
name
=
'LogConsumeEnabled'
onChange
=
{
handleInputChange
}
/
>
<
Form
.
Button
onClick
=
{()
=>
{
<
Form
.
Button
onClick
=
{()
=>
{
submitConfig
(
'ratio'
).
then
();
submitConfig
(
'ratio'
).
then
();
}}
>
保存倍率设置
<
/Form.Button
>
}}
>
保存倍率设置
<
/Form.Button
>
...
...
web/src/components/RedemptionsTable.js
View file @
a6cc0226
...
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
...
@@ -4,6 +4,7 @@ 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'
;
function
renderTimestamp
(
timestamp
)
{
function
renderTimestamp
(
timestamp
)
{
return
(
return
(
...
@@ -220,7 +221,7 @@ const RedemptionsTable = () => {
...
@@ -220,7 +221,7 @@ const RedemptionsTable = () => {
<
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
>
{
re
demption
.
quota
}
<
/Table.Cell
>
<
Table
.
Cell
>
{
re
nderQuota
(
redemption
.
quota
)
}
<
/Table.Cell
>
<
Table
.
Cell
>
{
renderTimestamp
(
redemption
.
created_time
)}
<
/Table.Cell
>
<
Table
.
Cell
>
{
renderTimestamp
(
redemption
.
created_time
)}
<
/Table.Cell
>
<
Table
.
Cell
>
{
redemption
.
redeemed_time
?
renderTimestamp
(
redemption
.
redeemed_time
)
:
"尚未兑换"
}
<
/Table.Cell
>
<
Table
.
Cell
>
{
redemption
.
redeemed_time
?
renderTimestamp
(
redemption
.
redeemed_time
)
:
"尚未兑换"
}
<
/Table.Cell
>
<
Table
.
Cell
>
<
Table
.
Cell
>
...
...
web/src/components/TokensTable.js
View file @
a6cc0226
...
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
...
@@ -4,6 +4,7 @@ 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'
;
function
renderTimestamp
(
timestamp
)
{
function
renderTimestamp
(
timestamp
)
{
return
(
return
(
...
@@ -220,7 +221,7 @@ const TokensTable = () => {
...
@@ -220,7 +221,7 @@ const TokensTable = () => {
<
Table
.
Row
key
=
{
token
.
id
}
>
<
Table
.
Row
key
=
{
token
.
id
}
>
<
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
>
{
token
.
unlimited_quota
?
'无限制'
:
token
.
remain_quota
}
<
/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
>
...
...
web/src/components/UsersTable.js
View file @
a6cc0226
...
@@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
...
@@ -4,7 +4,7 @@ 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
,
renderText
}
from
'../helpers/render'
;
import
{
renderGroup
,
renderNumber
,
render
Quota
,
render
Text
}
from
'../helpers/render'
;
function
renderRole
(
role
)
{
function
renderRole
(
role
)
{
switch
(
role
)
{
switch
(
role
)
{
...
@@ -244,8 +244,8 @@ const UsersTable = () => {
...
@@ -244,8 +244,8 @@ 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
>
{
render
Number
(
user
.
quota
)}
<
/Label>} /
>
<
Popup
content
=
'剩余额度'
trigger
=
{
<
Label
>
{
render
Quota
(
user
.
quota
)}
<
/Label>} /
>
<
Popup
content
=
'已用额度'
trigger
=
{
<
Label
>
{
render
Number
(
user
.
used_quota
)}
<
/Label>} /
>
<
Popup
content
=
'已用额度'
trigger
=
{
<
Label
>
{
render
Quota
(
user
.
used_quota
)}
<
/Label>} /
>
<
Popup
content
=
'请求次数'
trigger
=
{
<
Label
>
{
renderNumber
(
user
.
request_count
)}
<
/Label>} /
>
<
Popup
content
=
'请求次数'
trigger
=
{
<
Label
>
{
renderNumber
(
user
.
request_count
)}
<
/Label>} /
>
<
/Table.Cell
>
<
/Table.Cell
>
<
Table
.
Cell
>
{
renderRole
(
user
.
role
)}
<
/Table.Cell
>
<
Table
.
Cell
>
{
renderRole
(
user
.
role
)}
<
/Table.Cell
>
...
...
web/src/helpers/render.js
View file @
a6cc0226
...
@@ -35,4 +35,15 @@ export function renderNumber(num) {
...
@@ -35,4 +35,15 @@ export function renderNumber(num) {
}
else
{
}
else
{
return
num
;
return
num
;
}
}
}
export
function
renderQuota
(
quota
,
digits
=
2
)
{
let
quotaPerUnit
=
localStorage
.
getItem
(
'quota_per_unit'
);
let
displayInCurrency
=
localStorage
.
getItem
(
'display_in_currency'
);
quotaPerUnit
=
parseFloat
(
quotaPerUnit
);
displayInCurrency
=
displayInCurrency
===
'true'
;
if
(
displayInCurrency
)
{
return
'$'
+
(
quota
/
quotaPerUnit
).
toFixed
(
digits
);
}
return
renderNumber
(
quota
);
}
}
\ No newline at end of file
web/src/pages/Redemption/EditRedemption.js
View file @
a6cc0226
...
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
...
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import
{
Button
,
Form
,
Header
,
Segment
}
from
'semantic-ui-react'
;
import
{
Button
,
Form
,
Header
,
Segment
}
from
'semantic-ui-react'
;
import
{
useParams
}
from
'react-router-dom'
;
import
{
useParams
}
from
'react-router-dom'
;
import
{
API
,
downloadTextAsFile
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
API
,
downloadTextAsFile
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
renderQuota
}
from
'../../helpers/render'
;
const
EditRedemption
=
()
=>
{
const
EditRedemption
=
()
=>
{
const
params
=
useParams
();
const
params
=
useParams
();
...
@@ -87,7 +88,7 @@ const EditRedemption = () => {
...
@@ -87,7 +88,7 @@ const EditRedemption = () => {
<
/Form.Field
>
<
/Form.Field
>
<
Form
.
Field
>
<
Form
.
Field
>
<
Form
.
Input
<
Form
.
Input
label
=
'额度'
label
=
{
`额度(等价金额
${
renderQuota
(
quota
)}
)`
}
name
=
'quota'
name
=
'quota'
placeholder
=
{
'请输入单个兑换码中包含的额度'
}
placeholder
=
{
'请输入单个兑换码中包含的额度'
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
...
...
web/src/pages/Token/EditToken.js
View file @
a6cc0226
...
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
...
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import
{
Button
,
Form
,
Header
,
Message
,
Segment
}
from
'semantic-ui-react'
;
import
{
Button
,
Form
,
Header
,
Message
,
Segment
}
from
'semantic-ui-react'
;
import
{
useParams
}
from
'react-router-dom'
;
import
{
useParams
}
from
'react-router-dom'
;
import
{
API
,
showError
,
showSuccess
,
timestamp2string
}
from
'../../helpers'
;
import
{
API
,
showError
,
showSuccess
,
timestamp2string
}
from
'../../helpers'
;
import
{
renderQuota
}
from
'../../helpers/render'
;
const
EditToken
=
()
=>
{
const
EditToken
=
()
=>
{
const
params
=
useParams
();
const
params
=
useParams
();
...
@@ -137,7 +138,7 @@ const EditToken = () => {
...
@@ -137,7 +138,7 @@ const EditToken = () => {
<
Message
>
注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。
<
/Message
>
<
Message
>
注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。
<
/Message
>
<
Form
.
Field
>
<
Form
.
Field
>
<
Form
.
Input
<
Form
.
Input
label
=
'额度'
label
=
{
`额度(等价金额
${
renderQuota
(
remain_quota
)}
)`
}
name
=
'remain_quota'
name
=
'remain_quota'
placeholder
=
{
'请输入额度'
}
placeholder
=
{
'请输入额度'
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
...
...
web/src/pages/TopUp/index.js
View file @
a6cc0226
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'
;
const
TopUp
=
()
=>
{
const
TopUp
=
()
=>
{
const
[
redemptionCode
,
setRedemptionCode
]
=
useState
(
''
);
const
[
redemptionCode
,
setRedemptionCode
]
=
useState
(
''
);
...
@@ -81,7 +82,7 @@ const TopUp = () => {
...
@@ -81,7 +82,7 @@ const TopUp = () => {
<
Grid
.
Column
>
<
Grid
.
Column
>
<
Statistic
.
Group
widths
=
'one'
>
<
Statistic
.
Group
widths
=
'one'
>
<
Statistic
>
<
Statistic
>
<
Statistic
.
Value
>
{
userQuota
.
toLocaleString
(
)}
<
/Statistic.Value
>
<
Statistic
.
Value
>
{
renderQuota
(
userQuota
)}
<
/Statistic.Value
>
<
Statistic
.
Label
>
剩余额度
<
/Statistic.Label
>
<
Statistic
.
Label
>
剩余额度
<
/Statistic.Label
>
<
/Statistic
>
<
/Statistic
>
<
/Statistic.Group
>
<
/Statistic.Group
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment