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
8c4f0e69
authored
Aug 09, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/alpha' into alpha
parents
2fb3e265
2da040f7
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
260 additions
and
188 deletions
+260
-188
model/main.go
+20
-9
web/src/components/common/ui/JSONEditor.js
+57
-27
web/src/components/table/tokens/TokensColumnDefs.js
+114
-96
web/src/components/table/users/UsersColumnDefs.js
+69
-56
No files found.
model/main.go
View file @
8c4f0e69
...
@@ -64,6 +64,22 @@ var DB *gorm.DB
...
@@ -64,6 +64,22 @@ var DB *gorm.DB
var
LOG_DB
*
gorm
.
DB
var
LOG_DB
*
gorm
.
DB
// dropIndexIfExists drops a MySQL index only if it exists to avoid noisy 1091 errors
func
dropIndexIfExists
(
tableName
string
,
indexName
string
)
{
if
!
common
.
UsingMySQL
{
return
}
var
count
int64
// Check index existence via information_schema
err
:=
DB
.
Raw
(
"SELECT COUNT(1) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?"
,
tableName
,
indexName
,
)
.
Scan
(
&
count
)
.
Error
if
err
==
nil
&&
count
>
0
{
_
=
DB
.
Exec
(
"ALTER TABLE "
+
tableName
+
" DROP INDEX "
+
indexName
+
";"
)
.
Error
}
}
func
createRootAccountIfNeed
()
error
{
func
createRootAccountIfNeed
()
error
{
var
user
User
var
user
User
//if user.Status != common.UserStatusEnabled {
//if user.Status != common.UserStatusEnabled {
...
@@ -236,11 +252,8 @@ func InitLogDB() (err error) {
...
@@ -236,11 +252,8 @@ func InitLogDB() (err error) {
func
migrateDB
()
error
{
func
migrateDB
()
error
{
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
if
common
.
UsingMySQL
{
dropIndexIfExists
(
"models"
,
"uk_model_name"
)
// 旧索引可能不存在,忽略删除错误即可
dropIndexIfExists
(
"vendors"
,
"uk_vendor_name"
)
_
=
DB
.
Exec
(
"ALTER TABLE models DROP INDEX uk_model_name;"
)
.
Error
_
=
DB
.
Exec
(
"ALTER TABLE vendors DROP INDEX uk_vendor_name;"
)
.
Error
}
if
!
common
.
UsingPostgreSQL
{
if
!
common
.
UsingPostgreSQL
{
return
migrateDBFast
()
return
migrateDBFast
()
}
}
...
@@ -271,10 +284,8 @@ func migrateDB() error {
...
@@ -271,10 +284,8 @@ func migrateDB() error {
func
migrateDBFast
()
error
{
func
migrateDBFast
()
error
{
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
if
common
.
UsingMySQL
{
dropIndexIfExists
(
"models"
,
"uk_model_name"
)
_
=
DB
.
Exec
(
"ALTER TABLE models DROP INDEX uk_model_name;"
)
.
Error
dropIndexIfExists
(
"vendors"
,
"uk_vendor_name"
)
_
=
DB
.
Exec
(
"ALTER TABLE vendors DROP INDEX uk_vendor_name;"
)
.
Error
}
var
wg
sync
.
WaitGroup
var
wg
sync
.
WaitGroup
...
...
web/src/components/common/ui/JSONEditor.js
View file @
8c4f0e69
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
,
{
useState
,
useEffect
,
useCallback
,
useMemo
}
from
'react'
;
import
React
,
{
useState
,
useEffect
,
useCallback
,
useMemo
}
from
'react'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
import
{
...
@@ -18,15 +37,19 @@ import {
...
@@ -18,15 +37,19 @@ import {
Tooltip
,
Tooltip
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
{
import
{
IconCode
,
IconPlus
,
IconPlus
,
IconDelete
,
IconDelete
,
IconRefresh
,
IconAlertTriangle
,
IconAlertTriangle
,
}
from
'@douyinfe/semi-icons'
;
}
from
'@douyinfe/semi-icons'
;
const
{
Text
}
=
Typography
;
const
{
Text
}
=
Typography
;
// 唯一 ID 生成器,确保在组件生命周期内稳定且递增
const
generateUniqueId
=
(()
=>
{
let
counter
=
0
;
return
()
=>
`kv_
${
counter
++
}
`
;
})();
const
JSONEditor
=
({
const
JSONEditor
=
({
value
=
''
,
value
=
''
,
onChange
,
onChange
,
...
@@ -46,13 +69,20 @@ const JSONEditor = ({
...
@@ -46,13 +69,20 @@ const JSONEditor = ({
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
// 将对象转换为键值对数组(包含唯一ID)
// 将对象转换为键值对数组(包含唯一ID)
const
objectToKeyValueArray
=
useCallback
((
obj
)
=>
{
const
objectToKeyValueArray
=
useCallback
((
obj
,
prevPairs
=
[]
)
=>
{
if
(
!
obj
||
typeof
obj
!==
'object'
)
return
[];
if
(
!
obj
||
typeof
obj
!==
'object'
)
return
[];
return
Object
.
entries
(
obj
).
map
(([
key
,
value
],
index
)
=>
({
id
:
`
${
Date
.
now
()}
_
${
index
}
_
${
Math
.
random
()}
`
,
// 唯一ID
const
entries
=
Object
.
entries
(
obj
);
key
,
return
entries
.
map
(([
key
,
value
],
index
)
=>
{
value
// 如果上一次转换后同位置的键一致,则沿用其 id,保持 React key 稳定
}));
const
prev
=
prevPairs
[
index
];
const
shouldReuseId
=
prev
&&
prev
.
key
===
key
;
return
{
id
:
shouldReuseId
?
prev
.
id
:
generateUniqueId
(),
key
,
value
,
};
});
},
[]);
},
[]);
// 将键值对数组转换为对象(重复键时后面的会覆盖前面的)
// 将键值对数组转换为对象(重复键时后面的会覆盖前面的)
...
@@ -102,14 +132,14 @@ const JSONEditor = ({
...
@@ -102,14 +132,14 @@ const JSONEditor = ({
}
}
return
'visual'
;
return
'visual'
;
});
});
const
[
jsonError
,
setJsonError
]
=
useState
(
''
);
const
[
jsonError
,
setJsonError
]
=
useState
(
''
);
// 计算重复的键
// 计算重复的键
const
duplicateKeys
=
useMemo
(()
=>
{
const
duplicateKeys
=
useMemo
(()
=>
{
const
keyCount
=
{};
const
keyCount
=
{};
const
duplicates
=
new
Set
();
const
duplicates
=
new
Set
();
keyValuePairs
.
forEach
(
pair
=>
{
keyValuePairs
.
forEach
(
pair
=>
{
if
(
pair
.
key
)
{
if
(
pair
.
key
)
{
keyCount
[
pair
.
key
]
=
(
keyCount
[
pair
.
key
]
||
0
)
+
1
;
keyCount
[
pair
.
key
]
=
(
keyCount
[
pair
.
key
]
||
0
)
+
1
;
...
@@ -118,7 +148,7 @@ const JSONEditor = ({
...
@@ -118,7 +148,7 @@ const JSONEditor = ({
}
}
}
}
});
});
return
duplicates
;
return
duplicates
;
},
[
keyValuePairs
]);
},
[
keyValuePairs
]);
...
@@ -131,11 +161,11 @@ const JSONEditor = ({
...
@@ -131,11 +161,11 @@ const JSONEditor = ({
}
else
if
(
typeof
value
===
'object'
&&
value
!==
null
)
{
}
else
if
(
typeof
value
===
'object'
&&
value
!==
null
)
{
parsed
=
value
;
parsed
=
value
;
}
}
// 只在外部值真正改变时更新,避免循环更新
// 只在外部值真正改变时更新,避免循环更新
const
currentObj
=
keyValueArrayToObject
(
keyValuePairs
);
const
currentObj
=
keyValueArrayToObject
(
keyValuePairs
);
if
(
JSON
.
stringify
(
parsed
)
!==
JSON
.
stringify
(
currentObj
))
{
if
(
JSON
.
stringify
(
parsed
)
!==
JSON
.
stringify
(
currentObj
))
{
setKeyValuePairs
(
objectToKeyValueArray
(
parsed
));
setKeyValuePairs
(
objectToKeyValueArray
(
parsed
,
keyValuePairs
));
}
}
setJsonError
(
''
);
setJsonError
(
''
);
}
catch
(
error
)
{
}
catch
(
error
)
{
...
@@ -158,7 +188,7 @@ const JSONEditor = ({
...
@@ -158,7 +188,7 @@ const JSONEditor = ({
setKeyValuePairs
(
newPairs
);
setKeyValuePairs
(
newPairs
);
const
jsonObject
=
keyValueArrayToObject
(
newPairs
);
const
jsonObject
=
keyValueArrayToObject
(
newPairs
);
const
jsonString
=
Object
.
keys
(
jsonObject
).
length
===
0
?
''
:
JSON
.
stringify
(
jsonObject
,
null
,
2
);
const
jsonString
=
Object
.
keys
(
jsonObject
).
length
===
0
?
''
:
JSON
.
stringify
(
jsonObject
,
null
,
2
);
setJsonError
(
''
);
setJsonError
(
''
);
// 通过formApi设置值
// 通过formApi设置值
...
@@ -175,7 +205,7 @@ const JSONEditor = ({
...
@@ -175,7 +205,7 @@ const JSONEditor = ({
if
(
newValue
&&
newValue
.
trim
())
{
if
(
newValue
&&
newValue
.
trim
())
{
try
{
try
{
const
parsed
=
JSON
.
parse
(
newValue
);
const
parsed
=
JSON
.
parse
(
newValue
);
setKeyValuePairs
(
objectToKeyValueArray
(
parsed
));
setKeyValuePairs
(
objectToKeyValueArray
(
parsed
,
keyValuePairs
));
setJsonError
(
''
);
setJsonError
(
''
);
onChange
?.(
newValue
);
onChange
?.(
newValue
);
}
catch
(
error
)
{
}
catch
(
error
)
{
...
@@ -186,7 +216,7 @@ const JSONEditor = ({
...
@@ -186,7 +216,7 @@ const JSONEditor = ({
setJsonError
(
''
);
setJsonError
(
''
);
onChange
?.(
''
);
onChange
?.(
''
);
}
}
},
[
onChange
,
objectToKeyValueArray
]);
},
[
onChange
,
objectToKeyValueArray
,
keyValuePairs
]);
// 切换编辑模式
// 切换编辑模式
const
toggleEditMode
=
useCallback
(()
=>
{
const
toggleEditMode
=
useCallback
(()
=>
{
...
@@ -204,7 +234,7 @@ const JSONEditor = ({
...
@@ -204,7 +234,7 @@ const JSONEditor = ({
}
else
if
(
typeof
value
===
'object'
&&
value
!==
null
)
{
}
else
if
(
typeof
value
===
'object'
&&
value
!==
null
)
{
parsed
=
value
;
parsed
=
value
;
}
}
setKeyValuePairs
(
objectToKeyValueArray
(
parsed
));
setKeyValuePairs
(
objectToKeyValueArray
(
parsed
,
keyValuePairs
));
setJsonError
(
''
);
setJsonError
(
''
);
setEditMode
(
'visual'
);
setEditMode
(
'visual'
);
}
catch
(
error
)
{
}
catch
(
error
)
{
...
@@ -225,7 +255,7 @@ const JSONEditor = ({
...
@@ -225,7 +255,7 @@ const JSONEditor = ({
newKey
=
`field_
${
counter
}
`
;
newKey
=
`field_
${
counter
}
`
;
}
}
newPairs
.
push
({
newPairs
.
push
({
id
:
`
${
Date
.
now
()}
_
${
Math
.
random
()}
`
,
id
:
generateUniqueId
()
,
key
:
newKey
,
key
:
newKey
,
value
:
''
value
:
''
});
});
...
@@ -240,7 +270,7 @@ const JSONEditor = ({
...
@@ -240,7 +270,7 @@ const JSONEditor = ({
// 更新键名
// 更新键名
const
updateKey
=
useCallback
((
id
,
newKey
)
=>
{
const
updateKey
=
useCallback
((
id
,
newKey
)
=>
{
const
newPairs
=
keyValuePairs
.
map
(
pair
=>
const
newPairs
=
keyValuePairs
.
map
(
pair
=>
pair
.
id
===
id
?
{
...
pair
,
key
:
newKey
}
:
pair
pair
.
id
===
id
?
{
...
pair
,
key
:
newKey
}
:
pair
);
);
handleVisualChange
(
newPairs
);
handleVisualChange
(
newPairs
);
...
@@ -264,11 +294,11 @@ const JSONEditor = ({
...
@@ -264,11 +294,11 @@ const JSONEditor = ({
}
}
setManualText
(
templateString
);
setManualText
(
templateString
);
setKeyValuePairs
(
objectToKeyValueArray
(
template
));
setKeyValuePairs
(
objectToKeyValueArray
(
template
,
keyValuePairs
));
onChange
?.(
templateString
);
onChange
?.(
templateString
);
setJsonError
(
''
);
setJsonError
(
''
);
}
}
},
[
template
,
onChange
,
formApi
,
field
,
objectToKeyValueArray
]);
},
[
template
,
onChange
,
formApi
,
field
,
objectToKeyValueArray
,
keyValuePairs
]);
// 渲染值输入控件(支持嵌套)
// 渲染值输入控件(支持嵌套)
const
renderValueInput
=
(
pairId
,
value
)
=>
{
const
renderValueInput
=
(
pairId
,
value
)
=>
{
...
@@ -327,7 +357,7 @@ const JSONEditor = ({
...
@@ -327,7 +357,7 @@ const JSONEditor = ({
let
convertedValue
=
newValue
;
let
convertedValue
=
newValue
;
if
(
newValue
===
'true'
)
convertedValue
=
true
;
if
(
newValue
===
'true'
)
convertedValue
=
true
;
else
if
(
newValue
===
'false'
)
convertedValue
=
false
;
else
if
(
newValue
===
'false'
)
convertedValue
=
false
;
else
if
(
!
isNaN
(
newValue
)
&&
newValue
!==
''
&&
newValue
!==
'0'
)
{
else
if
(
!
isNaN
(
newValue
)
&&
newValue
!==
''
)
{
const
num
=
Number
(
newValue
);
const
num
=
Number
(
newValue
);
// 检查是否为整数
// 检查是否为整数
if
(
Number
.
isInteger
(
num
))
{
if
(
Number
.
isInteger
(
num
))
{
...
@@ -373,9 +403,9 @@ const JSONEditor = ({
...
@@ -373,9 +403,9 @@ const JSONEditor = ({
{
keyValuePairs
.
map
((
pair
,
index
)
=>
{
{
keyValuePairs
.
map
((
pair
,
index
)
=>
{
const
isDuplicate
=
duplicateKeys
.
has
(
pair
.
key
);
const
isDuplicate
=
duplicateKeys
.
has
(
pair
.
key
);
const
isLastDuplicate
=
isDuplicate
&&
const
isLastDuplicate
=
isDuplicate
&&
keyValuePairs
.
slice
(
index
+
1
).
every
(
p
=>
p
.
key
!==
pair
.
key
);
keyValuePairs
.
slice
(
index
+
1
).
every
(
p
=>
p
.
key
!==
pair
.
key
);
return
(
return
(
<
Row
key
=
{
pair
.
id
}
gutter
=
{
8
}
align
=
"middle"
>
<
Row
key
=
{
pair
.
id
}
gutter
=
{
8
}
align
=
"middle"
>
<
Col
span
=
{
6
}
>
<
Col
span
=
{
6
}
>
...
@@ -389,14 +419,14 @@ const JSONEditor = ({
...
@@ -389,14 +419,14 @@ const JSONEditor = ({
{
isDuplicate
&&
(
{
isDuplicate
&&
(
<
Tooltip
<
Tooltip
content
=
{
content
=
{
isLastDuplicate
isLastDuplicate
?
t
(
'这是重复键中的最后一个,其值将被使用'
)
?
t
(
'这是重复键中的最后一个,其值将被使用'
)
:
t
(
'重复的键名,此值将被后面的同名键覆盖'
)
:
t
(
'重复的键名,此值将被后面的同名键覆盖'
)
}
}
>
>
<
IconAlertTriangle
<
IconAlertTriangle
className
=
"absolute right-2 top-1/2 transform -translate-y-1/2"
className
=
"absolute right-2 top-1/2 transform -translate-y-1/2"
style
=
{{
style
=
{{
color
:
isLastDuplicate
?
'#ff7d00'
:
'#faad14'
,
color
:
isLastDuplicate
?
'#ff7d00'
:
'#faad14'
,
fontSize
:
'14px'
fontSize
:
'14px'
}}
}}
...
@@ -471,7 +501,7 @@ const JSONEditor = ({
...
@@ -471,7 +501,7 @@ const JSONEditor = ({
updateValue
(
defaultPair
.
id
,
value
);
updateValue
(
defaultPair
.
id
,
value
);
}
else
{
}
else
{
const
newPairs
=
[...
keyValuePairs
,
{
const
newPairs
=
[...
keyValuePairs
,
{
id
:
`
${
Date
.
now
()}
_
${
Math
.
random
()}
`
,
id
:
generateUniqueId
()
,
key
:
'default'
,
key
:
'default'
,
value
:
value
value
:
value
}];
}];
...
...
web/src/components/table/tokens/TokensColumnDefs.js
View file @
8c4f0e69
...
@@ -28,7 +28,8 @@ import {
...
@@ -28,7 +28,8 @@ import {
Avatar
,
Avatar
,
Tooltip
,
Tooltip
,
Progress
,
Progress
,
Switch
,
Popover
,
Typography
,
Input
,
Input
,
Modal
Modal
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
...
@@ -46,21 +47,22 @@ import {
...
@@ -46,21 +47,22 @@ import {
IconEyeClosed
,
IconEyeClosed
,
}
from
'@douyinfe/semi-icons'
;
}
from
'@douyinfe/semi-icons'
;
// progress color helper
const
getProgressColor
=
(
pct
)
=>
{
if
(
pct
===
100
)
return
'var(--semi-color-success)'
;
if
(
pct
<=
10
)
return
'var(--semi-color-danger)'
;
if
(
pct
<=
30
)
return
'var(--semi-color-warning)'
;
return
undefined
;
};
// Render functions
// Render functions
function
renderTimestamp
(
timestamp
)
{
function
renderTimestamp
(
timestamp
)
{
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
}
}
// Render status column
with switch and progress bar
// Render status column
only (no usage)
const
renderStatus
=
(
text
,
record
,
manageToken
,
t
)
=>
{
const
renderStatus
=
(
text
,
record
,
t
)
=>
{
const
enabled
=
text
===
1
;
const
enabled
=
text
===
1
;
const
handleToggle
=
(
checked
)
=>
{
if
(
checked
)
{
manageToken
(
record
.
id
,
'enable'
,
record
);
}
else
{
manageToken
(
record
.
id
,
'disable'
,
record
);
}
};
let
tagColor
=
'black'
;
let
tagColor
=
'black'
;
let
tagText
=
t
(
'未知状态'
);
let
tagText
=
t
(
'未知状态'
);
...
@@ -78,69 +80,11 @@ const renderStatus = (text, record, manageToken, t) => {
...
@@ -78,69 +80,11 @@ const renderStatus = (text, record, manageToken, t) => {
tagText
=
t
(
'已耗尽'
);
tagText
=
t
(
'已耗尽'
);
}
}
const
used
=
parseInt
(
record
.
used_quota
)
||
0
;
return
(
const
remain
=
parseInt
(
record
.
remain_quota
)
||
0
;
<
Tag
color
=
{
tagColor
}
shape
=
'circle'
size
=
'small'
>
const
total
=
used
+
remain
;
const
percent
=
total
>
0
?
(
remain
/
total
)
*
100
:
0
;
const
getProgressColor
=
(
pct
)
=>
{
if
(
pct
===
100
)
return
'var(--semi-color-success)'
;
if
(
pct
<=
10
)
return
'var(--semi-color-danger)'
;
if
(
pct
<=
30
)
return
'var(--semi-color-warning)'
;
return
undefined
;
};
const
quotaSuffix
=
record
.
unlimited_quota
?
(
<
div
className
=
'text-xs'
>
{
t
(
'无限额度'
)}
<
/div
>
)
:
(
<
div
className
=
'flex flex-col items-end'
>
<
span
className
=
'text-xs leading-none'
>
{
`
${
renderQuota
(
remain
)}
/
${
renderQuota
(
total
)}
`
}
<
/span
>
<
Progress
percent
=
{
percent
}
stroke
=
{
getProgressColor
(
percent
)}
aria
-
label
=
'quota usage'
format
=
{()
=>
`
${
percent
.
toFixed
(
0
)}
%`
}
style
=
{{
width
:
'100%'
,
marginTop
:
'1px'
,
marginBottom
:
0
}}
/
>
<
/div
>
);
const
content
=
(
<
Tag
color
=
{
tagColor
}
shape
=
'circle'
size
=
'large'
prefixIcon
=
{
<
Switch
size
=
'small'
checked
=
{
enabled
}
onChange
=
{
handleToggle
}
aria
-
label
=
'token status switch'
/>
}
suffixIcon
=
{
quotaSuffix
}
>
{
tagText
}
{
tagText
}
<
/Tag
>
<
/Tag
>
);
);
const
tooltipContent
=
record
.
unlimited_quota
?
(
<
div
className
=
'text-xs'
>
<
div
>
{
t
(
'已用额度'
)}:
{
renderQuota
(
used
)}
<
/div
>
<
/div
>
)
:
(
<
div
className
=
'text-xs'
>
<
div
>
{
t
(
'已用额度'
)}:
{
renderQuota
(
used
)}
<
/div
>
<
div
>
{
t
(
'剩余额度'
)}:
{
renderQuota
(
remain
)}
({
percent
.
toFixed
(
0
)}
%
)
<
/div
>
<
div
>
{
t
(
'总额度'
)}:
{
renderQuota
(
total
)}
<
/div
>
<
/div
>
);
return
(
<
Tooltip
content
=
{
tooltipContent
}
>
{
content
}
<
/Tooltip
>
);
};
};
// Render group column
// Render group column
...
@@ -292,35 +236,81 @@ const renderAllowIps = (text, t) => {
...
@@ -292,35 +236,81 @@ const renderAllowIps = (text, t) => {
return
<
Space
wrap
>
{
ipTags
}
<
/Space>
;
return
<
Space
wrap
>
{
ipTags
}
<
/Space>
;
};
};
// Render separate quota usage column
const
renderQuotaUsage
=
(
text
,
record
,
t
)
=>
{
const
{
Paragraph
}
=
Typography
;
const
used
=
parseInt
(
record
.
used_quota
)
||
0
;
const
remain
=
parseInt
(
record
.
remain_quota
)
||
0
;
const
total
=
used
+
remain
;
if
(
record
.
unlimited_quota
)
{
const
popoverContent
=
(
<
div
className
=
'text-xs p-2'
>
<
Paragraph
copyable
=
{{
content
:
renderQuota
(
used
)
}}
>
{
t
(
'已用额度'
)}:
{
renderQuota
(
used
)}
<
/Paragraph
>
<
/div
>
);
return
(
<
Popover
content
=
{
popoverContent
}
position
=
'top'
>
<
Tag
color
=
'white'
shape
=
'circle'
>
{
t
(
'无限额度'
)}
<
/Tag
>
<
/Popover
>
);
}
const
percent
=
total
>
0
?
(
remain
/
total
)
*
100
:
0
;
const
popoverContent
=
(
<
div
className
=
'text-xs p-2'
>
<
Paragraph
copyable
=
{{
content
:
renderQuota
(
used
)
}}
>
{
t
(
'已用额度'
)}:
{
renderQuota
(
used
)}
<
/Paragraph
>
<
Paragraph
copyable
=
{{
content
:
renderQuota
(
remain
)
}}
>
{
t
(
'剩余额度'
)}:
{
renderQuota
(
remain
)}
({
percent
.
toFixed
(
0
)}
%
)
<
/Paragraph
>
<
Paragraph
copyable
=
{{
content
:
renderQuota
(
total
)
}}
>
{
t
(
'总额度'
)}:
{
renderQuota
(
total
)}
<
/Paragraph
>
<
/div
>
);
return
(
<
Popover
content
=
{
popoverContent
}
position
=
'top'
>
<
Tag
color
=
'white'
shape
=
'circle'
>
<
div
className
=
'flex flex-col items-end'
>
<
span
className
=
'text-xs leading-none'
>
{
`
${
renderQuota
(
remain
)}
/
${
renderQuota
(
total
)}
`
}
<
/span
>
<
Progress
percent
=
{
percent
}
stroke
=
{
getProgressColor
(
percent
)}
aria
-
label
=
'quota usage'
format
=
{()
=>
`
${
percent
.
toFixed
(
0
)}
%`
}
style
=
{{
width
:
'100%'
,
marginTop
:
'1px'
,
marginBottom
:
0
}}
/
>
<
/div
>
<
/Tag
>
<
/Popover
>
);
};
// Render operations column
// Render operations column
const
renderOperations
=
(
text
,
record
,
onOpenLink
,
setEditingToken
,
setShowEdit
,
manageToken
,
refresh
,
t
)
=>
{
const
renderOperations
=
(
text
,
record
,
onOpenLink
,
setEditingToken
,
setShowEdit
,
manageToken
,
refresh
,
t
)
=>
{
let
chats
=
localStorage
.
getItem
(
'chats'
);
let
chatsArray
=
[];
let
chatsArray
=
[];
let
shouldUseCustom
=
true
;
try
{
const
raw
=
localStorage
.
getItem
(
'chats'
);
if
(
shouldUseCustom
)
{
const
parsed
=
JSON
.
parse
(
raw
);
try
{
if
(
Array
.
isArray
(
parsed
))
{
chats
=
JSON
.
parse
(
chats
);
for
(
let
i
=
0
;
i
<
parsed
.
length
;
i
++
)
{
if
(
Array
.
isArray
(
chats
))
{
const
item
=
parsed
[
i
];
for
(
let
i
=
0
;
i
<
chats
.
length
;
i
++
)
{
const
name
=
Object
.
keys
(
item
)[
0
];
let
chat
=
{};
if
(
!
name
)
continue
;
chat
.
node
=
'item'
;
chatsArray
.
push
({
for
(
let
key
in
chats
[
i
])
{
node
:
'item'
,
if
(
chats
[
i
].
hasOwnProperty
(
key
))
{
key
:
i
,
chat
.
key
=
i
;
name
,
chat
.
name
=
key
;
onClick
:
()
=>
onOpenLink
(
name
,
item
[
name
],
record
),
chat
.
onClick
=
()
=>
{
});
onOpenLink
(
key
,
chats
[
i
][
key
],
record
);
};
}
}
chatsArray
.
push
(
chat
);
}
}
}
}
catch
(
e
)
{
console
.
log
(
e
);
showError
(
t
(
'聊天链接配置错误,请联系管理员'
));
}
}
}
catch
(
_
)
{
showError
(
t
(
'聊天链接配置错误,请联系管理员'
));
}
}
return
(
return
(
...
@@ -338,7 +328,7 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
...
@@ -338,7 +328,7 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
}
else
{
}
else
{
onOpenLink
(
onOpenLink
(
'default'
,
'default'
,
chats
[
0
][
Object
.
keys
(
chats
[
0
])[
0
]]
,
chats
Array
[
0
].
name
?
(
parsed
=>
parsed
)(
localStorage
.
getItem
(
'chats'
))
:
''
,
record
,
record
,
);
);
}
}
...
@@ -359,6 +349,29 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
...
@@ -359,6 +349,29 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
<
/Dropdown
>
<
/Dropdown
>
<
/SplitButtonGroup
>
<
/SplitButtonGroup
>
{
record
.
status
===
1
?
(
<
Button
type
=
'danger'
size
=
"small"
onClick
=
{
async
()
=>
{
await
manageToken
(
record
.
id
,
'disable'
,
record
);
await
refresh
();
}}
>
{
t
(
'禁用'
)}
<
/Button
>
)
:
(
<
Button
size
=
"small"
onClick
=
{
async
()
=>
{
await
manageToken
(
record
.
id
,
'enable'
,
record
);
await
refresh
();
}}
>
{
t
(
'启用'
)}
<
/Button
>
)}
<
Button
<
Button
type
=
'tertiary'
type
=
'tertiary'
size
=
"small"
size
=
"small"
...
@@ -412,7 +425,12 @@ export const getTokensColumns = ({
...
@@ -412,7 +425,12 @@ export const getTokensColumns = ({
title
:
t
(
'状态'
),
title
:
t
(
'状态'
),
dataIndex
:
'status'
,
dataIndex
:
'status'
,
key
:
'status'
,
key
:
'status'
,
render
:
(
text
,
record
)
=>
renderStatus
(
text
,
record
,
manageToken
,
t
),
render
:
(
text
,
record
)
=>
renderStatus
(
text
,
record
,
t
),
},
{
title
:
t
(
'剩余额度/总额度'
),
key
:
'quota_usage'
,
render
:
(
text
,
record
)
=>
renderQuotaUsage
(
text
,
record
,
t
),
},
},
{
{
title
:
t
(
'分组'
),
title
:
t
(
'分组'
),
...
...
web/src/components/table/users/UsersColumnDefs.js
View file @
8c4f0e69
...
@@ -24,7 +24,8 @@ import {
...
@@ -24,7 +24,8 @@ import {
Tag
,
Tag
,
Tooltip
,
Tooltip
,
Progress
,
Progress
,
Switch
,
Popover
,
Typography
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
{
renderGroup
,
renderNumber
,
renderQuota
}
from
'../../../helpers'
;
import
{
renderGroup
,
renderNumber
,
renderQuota
}
from
'../../../helpers'
;
...
@@ -89,7 +90,6 @@ const renderUsername = (text, record) => {
...
@@ -89,7 +90,6 @@ const renderUsername = (text, record) => {
* Render user statistics
* Render user statistics
*/
*/
const
renderStatistics
=
(
text
,
record
,
showEnableDisableModal
,
t
)
=>
{
const
renderStatistics
=
(
text
,
record
,
showEnableDisableModal
,
t
)
=>
{
const
enabled
=
record
.
status
===
1
;
const
isDeleted
=
record
.
DeletedAt
!==
null
;
const
isDeleted
=
record
.
DeletedAt
!==
null
;
// Determine tag text & color like original status column
// Determine tag text & color like original status column
...
@@ -100,60 +100,17 @@ const renderStatistics = (text, record, showEnableDisableModal, t) => {
...
@@ -100,60 +100,17 @@ const renderStatistics = (text, record, showEnableDisableModal, t) => {
tagText
=
t
(
'已注销'
);
tagText
=
t
(
'已注销'
);
}
else
if
(
record
.
status
===
1
)
{
}
else
if
(
record
.
status
===
1
)
{
tagColor
=
'green'
;
tagColor
=
'green'
;
tagText
=
t
(
'已
激活
'
);
tagText
=
t
(
'已
启用
'
);
}
else
if
(
record
.
status
===
2
)
{
}
else
if
(
record
.
status
===
2
)
{
tagColor
=
'red'
;
tagColor
=
'red'
;
tagText
=
t
(
'已
封禁
'
);
tagText
=
t
(
'已
禁用
'
);
}
}
const
handleToggle
=
(
checked
)
=>
{
if
(
checked
)
{
showEnableDisableModal
(
record
,
'enable'
);
}
else
{
showEnableDisableModal
(
record
,
'disable'
);
}
};
const
used
=
parseInt
(
record
.
used_quota
)
||
0
;
const
remain
=
parseInt
(
record
.
quota
)
||
0
;
const
total
=
used
+
remain
;
const
percent
=
total
>
0
?
(
remain
/
total
)
*
100
:
0
;
const
getProgressColor
=
(
pct
)
=>
{
if
(
pct
===
100
)
return
'var(--semi-color-success)'
;
if
(
pct
<=
10
)
return
'var(--semi-color-danger)'
;
if
(
pct
<=
30
)
return
'var(--semi-color-warning)'
;
return
undefined
;
};
const
quotaSuffix
=
(
<
div
className
=
'flex flex-col items-end'
>
<
span
className
=
'text-xs leading-none'
>
{
`
${
renderQuota
(
remain
)}
/
${
renderQuota
(
total
)}
`
}
<
/span
>
<
Progress
percent
=
{
percent
}
stroke
=
{
getProgressColor
(
percent
)}
aria
-
label
=
'quota usage'
format
=
{()
=>
`
${
percent
.
toFixed
(
0
)}
%`
}
style
=
{{
width
:
'100%'
,
marginTop
:
'1px'
,
marginBottom
:
0
}}
/
>
<
/div
>
);
const
content
=
(
const
content
=
(
<
Tag
<
Tag
color
=
{
tagColor
}
color
=
{
tagColor
}
shape
=
'circle'
shape
=
'circle'
size
=
'large'
size
=
'small'
prefixIcon
=
{
<
Switch
size
=
'small'
checked
=
{
enabled
}
onChange
=
{
handleToggle
}
disabled
=
{
isDeleted
}
aria
-
label
=
'user status switch'
/>
}
suffixIcon
=
{
quotaSuffix
}
>
>
{
tagText
}
{
tagText
}
<
/Tag
>
<
/Tag
>
...
@@ -161,9 +118,6 @@ const renderStatistics = (text, record, showEnableDisableModal, t) => {
...
@@ -161,9 +118,6 @@ const renderStatistics = (text, record, showEnableDisableModal, t) => {
const
tooltipContent
=
(
const
tooltipContent
=
(
<
div
className
=
'text-xs'
>
<
div
className
=
'text-xs'
>
<
div
>
{
t
(
'已用额度'
)}:
{
renderQuota
(
used
)}
<
/div
>
<
div
>
{
t
(
'剩余额度'
)}:
{
renderQuota
(
remain
)}
({
percent
.
toFixed
(
0
)}
%
)
<
/div
>
<
div
>
{
t
(
'总额度'
)}:
{
renderQuota
(
total
)}
<
/div
>
<
div
>
{
t
(
'调用次数'
)}:
{
renderNumber
(
record
.
request_count
)}
<
/div
>
<
div
>
{
t
(
'调用次数'
)}:
{
renderNumber
(
record
.
request_count
)}
<
/div
>
<
/div
>
<
/div
>
);
);
...
@@ -175,6 +129,43 @@ const renderStatistics = (text, record, showEnableDisableModal, t) => {
...
@@ -175,6 +129,43 @@ const renderStatistics = (text, record, showEnableDisableModal, t) => {
);
);
};
};
// Render separate quota usage column
const
renderQuotaUsage
=
(
text
,
record
,
t
)
=>
{
const
{
Paragraph
}
=
Typography
;
const
used
=
parseInt
(
record
.
used_quota
)
||
0
;
const
remain
=
parseInt
(
record
.
quota
)
||
0
;
const
total
=
used
+
remain
;
const
percent
=
total
>
0
?
(
remain
/
total
)
*
100
:
0
;
const
popoverContent
=
(
<
div
className
=
'text-xs p-2'
>
<
Paragraph
copyable
=
{{
content
:
renderQuota
(
used
)
}}
>
{
t
(
'已用额度'
)}:
{
renderQuota
(
used
)}
<
/Paragraph
>
<
Paragraph
copyable
=
{{
content
:
renderQuota
(
remain
)
}}
>
{
t
(
'剩余额度'
)}:
{
renderQuota
(
remain
)}
({
percent
.
toFixed
(
0
)}
%
)
<
/Paragraph
>
<
Paragraph
copyable
=
{{
content
:
renderQuota
(
total
)
}}
>
{
t
(
'总额度'
)}:
{
renderQuota
(
total
)}
<
/Paragraph
>
<
/div
>
);
return
(
<
Popover
content
=
{
popoverContent
}
position
=
'top'
>
<
Tag
color
=
'white'
shape
=
'circle'
>
<
div
className
=
'flex flex-col items-end'
>
<
span
className
=
'text-xs leading-none'
>
{
`
${
renderQuota
(
remain
)}
/
${
renderQuota
(
total
)}
`
}
<
/span
>
<
Progress
percent
=
{
percent
}
aria
-
label
=
'quota usage'
format
=
{()
=>
`
${
percent
.
toFixed
(
0
)}
%`
}
style
=
{{
width
:
'100%'
,
marginTop
:
'1px'
,
marginBottom
:
0
}}
/
>
<
/div
>
<
/Tag
>
<
/Popover
>
);
};
/**
/**
* Render invite information
* Render invite information
*/
*/
...
@@ -204,6 +195,7 @@ const renderOperations = (text, record, {
...
@@ -204,6 +195,7 @@ const renderOperations = (text, record, {
setShowEditUser
,
setShowEditUser
,
showPromoteModal
,
showPromoteModal
,
showDemoteModal
,
showDemoteModal
,
showEnableDisableModal
,
showDeleteModal
,
showDeleteModal
,
t
t
})
=>
{
})
=>
{
...
@@ -213,6 +205,22 @@ const renderOperations = (text, record, {
...
@@ -213,6 +205,22 @@ const renderOperations = (text, record, {
return
(
return
(
<
Space
>
<
Space
>
{
record
.
status
===
1
?
(
<
Button
type
=
'danger'
size
=
"small"
onClick
=
{()
=>
showEnableDisableModal
(
record
,
'disable'
)}
>
{
t
(
'禁用'
)}
<
/Button
>
)
:
(
<
Button
size
=
"small"
onClick
=
{()
=>
showEnableDisableModal
(
record
,
'enable'
)}
>
{
t
(
'启用'
)}
<
/Button
>
)}
<
Button
<
Button
type
=
'tertiary'
type
=
'tertiary'
size
=
"small"
size
=
"small"
...
@@ -271,6 +279,16 @@ export const getUsersColumns = ({
...
@@ -271,6 +279,16 @@ export const getUsersColumns = ({
render
:
(
text
,
record
)
=>
renderUsername
(
text
,
record
),
render
:
(
text
,
record
)
=>
renderUsername
(
text
,
record
),
},
},
{
{
title
:
t
(
'状态'
),
dataIndex
:
'info'
,
render
:
(
text
,
record
,
index
)
=>
renderStatistics
(
text
,
record
,
showEnableDisableModal
,
t
),
},
{
title
:
t
(
'剩余额度/总额度'
),
key
:
'quota_usage'
,
render
:
(
text
,
record
)
=>
renderQuotaUsage
(
text
,
record
,
t
),
},
{
title
:
t
(
'分组'
),
title
:
t
(
'分组'
),
dataIndex
:
'group'
,
dataIndex
:
'group'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
...
@@ -285,11 +303,6 @@ export const getUsersColumns = ({
...
@@ -285,11 +303,6 @@ export const getUsersColumns = ({
},
},
},
},
{
{
title
:
t
(
'状态'
),
dataIndex
:
'info'
,
render
:
(
text
,
record
,
index
)
=>
renderStatistics
(
text
,
record
,
showEnableDisableModal
,
t
),
},
{
title
:
t
(
'邀请信息'
),
title
:
t
(
'邀请信息'
),
dataIndex
:
'invite'
,
dataIndex
:
'invite'
,
render
:
(
text
,
record
,
index
)
=>
renderInviteInfo
(
text
,
record
,
t
),
render
:
(
text
,
record
,
index
)
=>
renderInviteInfo
(
text
,
record
,
t
),
...
...
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