Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
5f8e60a1
Commit
5f8e60a1
authored
Apr 09, 2026
by
IanShaw027
Browse files
feat(table): 表格排序与搜索改为后端处理
parent
66e15a54
Changes
79
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/admin_service.go
View file @
5f8e60a1
...
...
@@ -21,13 +21,13 @@ import (
// AdminService interface defines admin management operations
type
AdminService
interface
{
// User management
ListUsers
(
ctx
context
.
Context
,
page
,
pageSize
int
,
filters
UserListFilters
)
([]
User
,
int64
,
error
)
ListUsers
(
ctx
context
.
Context
,
page
,
pageSize
int
,
filters
UserListFilters
,
sortBy
,
sortOrder
string
)
([]
User
,
int64
,
error
)
GetUser
(
ctx
context
.
Context
,
id
int64
)
(
*
User
,
error
)
CreateUser
(
ctx
context
.
Context
,
input
*
CreateUserInput
)
(
*
User
,
error
)
UpdateUser
(
ctx
context
.
Context
,
id
int64
,
input
*
UpdateUserInput
)
(
*
User
,
error
)
DeleteUser
(
ctx
context
.
Context
,
id
int64
)
error
UpdateUserBalance
(
ctx
context
.
Context
,
userID
int64
,
balance
float64
,
operation
string
,
notes
string
)
(
*
User
,
error
)
GetUserAPIKeys
(
ctx
context
.
Context
,
userID
int64
,
page
,
pageSize
int
)
([]
APIKey
,
int64
,
error
)
GetUserAPIKeys
(
ctx
context
.
Context
,
userID
int64
,
page
,
pageSize
int
,
sortBy
,
sortOrder
string
)
([]
APIKey
,
int64
,
error
)
GetUserUsageStats
(
ctx
context
.
Context
,
userID
int64
,
period
string
)
(
any
,
error
)
// GetUserBalanceHistory returns paginated balance/concurrency change records for a user.
// codeType is optional - pass empty string to return all types.
...
...
@@ -35,7 +35,7 @@ type AdminService interface {
GetUserBalanceHistory
(
ctx
context
.
Context
,
userID
int64
,
page
,
pageSize
int
,
codeType
string
)
([]
RedeemCode
,
int64
,
float64
,
error
)
// Group management
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
Group
,
int64
,
error
)
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
,
search
string
,
isExclusive
*
bool
,
sortBy
,
sortOrder
string
)
([]
Group
,
int64
,
error
)
GetAllGroups
(
ctx
context
.
Context
)
([]
Group
,
error
)
GetAllGroupsByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Group
,
error
)
GetGroup
(
ctx
context
.
Context
,
id
int64
)
(
*
Group
,
error
)
...
...
@@ -55,7 +55,7 @@ type AdminService interface {
ReplaceUserGroup
(
ctx
context
.
Context
,
userID
,
oldGroupID
,
newGroupID
int64
)
(
*
ReplaceUserGroupResult
,
error
)
// Account management
ListAccounts
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
accountType
,
status
,
search
string
,
groupID
int64
,
privacyMode
string
)
([]
Account
,
int64
,
error
)
ListAccounts
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
accountType
,
status
,
search
string
,
groupID
int64
,
privacyMode
string
,
sortBy
,
sortOrder
string
)
([]
Account
,
int64
,
error
)
GetAccount
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
GetAccountsByIDs
(
ctx
context
.
Context
,
ids
[]
int64
)
([]
*
Account
,
error
)
CreateAccount
(
ctx
context
.
Context
,
input
*
CreateAccountInput
)
(
*
Account
,
error
)
...
...
@@ -77,8 +77,8 @@ type AdminService interface {
CheckMixedChannelRisk
(
ctx
context
.
Context
,
currentAccountID
int64
,
currentAccountPlatform
string
,
groupIDs
[]
int64
)
error
// Proxy management
ListProxies
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
)
([]
Proxy
,
int64
,
error
)
ListProxiesWithAccountCount
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
)
([]
ProxyWithAccountCount
,
int64
,
error
)
ListProxies
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
Proxy
,
int64
,
error
)
ListProxiesWithAccountCount
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
ProxyWithAccountCount
,
int64
,
error
)
GetAllProxies
(
ctx
context
.
Context
)
([]
Proxy
,
error
)
GetAllProxiesWithAccountCount
(
ctx
context
.
Context
)
([]
ProxyWithAccountCount
,
error
)
GetProxy
(
ctx
context
.
Context
,
id
int64
)
(
*
Proxy
,
error
)
...
...
@@ -93,7 +93,7 @@ type AdminService interface {
CheckProxyQuality
(
ctx
context
.
Context
,
id
int64
)
(
*
ProxyQualityCheckResult
,
error
)
// Redeem code management
ListRedeemCodes
(
ctx
context
.
Context
,
page
,
pageSize
int
,
codeType
,
status
,
search
string
)
([]
RedeemCode
,
int64
,
error
)
ListRedeemCodes
(
ctx
context
.
Context
,
page
,
pageSize
int
,
codeType
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
RedeemCode
,
int64
,
error
)
GetRedeemCode
(
ctx
context
.
Context
,
id
int64
)
(
*
RedeemCode
,
error
)
GenerateRedeemCodes
(
ctx
context
.
Context
,
input
*
GenerateRedeemCodesInput
)
([]
RedeemCode
,
error
)
DeleteRedeemCode
(
ctx
context
.
Context
,
id
int64
)
error
...
...
@@ -483,8 +483,8 @@ func NewAdminService(
}
// User management implementations
func
(
s
*
adminServiceImpl
)
ListUsers
(
ctx
context
.
Context
,
page
,
pageSize
int
,
filters
UserListFilters
)
([]
User
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
func
(
s
*
adminServiceImpl
)
ListUsers
(
ctx
context
.
Context
,
page
,
pageSize
int
,
filters
UserListFilters
,
sortBy
,
sortOrder
string
)
([]
User
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
}
users
,
result
,
err
:=
s
.
userRepo
.
ListWithFilters
(
ctx
,
params
,
filters
)
if
err
!=
nil
{
return
nil
,
0
,
err
...
...
@@ -751,8 +751,8 @@ func (s *adminServiceImpl) UpdateUserBalance(ctx context.Context, userID int64,
return
user
,
nil
}
func
(
s
*
adminServiceImpl
)
GetUserAPIKeys
(
ctx
context
.
Context
,
userID
int64
,
page
,
pageSize
int
)
([]
APIKey
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
func
(
s
*
adminServiceImpl
)
GetUserAPIKeys
(
ctx
context
.
Context
,
userID
int64
,
page
,
pageSize
int
,
sortBy
,
sortOrder
string
)
([]
APIKey
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
}
keys
,
result
,
err
:=
s
.
apiKeyRepo
.
ListByUserID
(
ctx
,
userID
,
params
,
APIKeyListFilters
{})
if
err
!=
nil
{
return
nil
,
0
,
err
...
...
@@ -787,8 +787,8 @@ func (s *adminServiceImpl) GetUserBalanceHistory(ctx context.Context, userID int
}
// Group management implementations
func
(
s
*
adminServiceImpl
)
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
Group
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
func
(
s
*
adminServiceImpl
)
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
,
search
string
,
isExclusive
*
bool
,
sortBy
,
sortOrder
string
)
([]
Group
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
}
groups
,
result
,
err
:=
s
.
groupRepo
.
ListWithFilters
(
ctx
,
params
,
platform
,
status
,
search
,
isExclusive
)
if
err
!=
nil
{
return
nil
,
0
,
err
...
...
@@ -1456,8 +1456,8 @@ func (s *adminServiceImpl) ReplaceUserGroup(ctx context.Context, userID, oldGrou
}
// Account management implementations
func
(
s
*
adminServiceImpl
)
ListAccounts
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
accountType
,
status
,
search
string
,
groupID
int64
,
privacyMode
string
)
([]
Account
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
func
(
s
*
adminServiceImpl
)
ListAccounts
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
accountType
,
status
,
search
string
,
groupID
int64
,
privacyMode
string
,
sortBy
,
sortOrder
string
)
([]
Account
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
}
accounts
,
result
,
err
:=
s
.
accountRepo
.
ListWithFilters
(
ctx
,
params
,
platform
,
accountType
,
status
,
search
,
groupID
,
privacyMode
)
if
err
!=
nil
{
return
nil
,
0
,
err
...
...
@@ -1885,8 +1885,8 @@ func (s *adminServiceImpl) SetAccountSchedulable(ctx context.Context, id int64,
}
// Proxy management implementations
func
(
s
*
adminServiceImpl
)
ListProxies
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
)
([]
Proxy
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
func
(
s
*
adminServiceImpl
)
ListProxies
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
Proxy
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
}
proxies
,
result
,
err
:=
s
.
proxyRepo
.
ListWithFilters
(
ctx
,
params
,
protocol
,
status
,
search
)
if
err
!=
nil
{
return
nil
,
0
,
err
...
...
@@ -1894,8 +1894,8 @@ func (s *adminServiceImpl) ListProxies(ctx context.Context, page, pageSize int,
return
proxies
,
result
.
Total
,
nil
}
func
(
s
*
adminServiceImpl
)
ListProxiesWithAccountCount
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
)
([]
ProxyWithAccountCount
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
func
(
s
*
adminServiceImpl
)
ListProxiesWithAccountCount
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
ProxyWithAccountCount
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
}
proxies
,
result
,
err
:=
s
.
proxyRepo
.
ListWithFiltersAndAccountCount
(
ctx
,
params
,
protocol
,
status
,
search
)
if
err
!=
nil
{
return
nil
,
0
,
err
...
...
@@ -2032,8 +2032,8 @@ func (s *adminServiceImpl) CheckProxyExists(ctx context.Context, host string, po
}
// Redeem code management implementations
func
(
s
*
adminServiceImpl
)
ListRedeemCodes
(
ctx
context
.
Context
,
page
,
pageSize
int
,
codeType
,
status
,
search
string
)
([]
RedeemCode
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
func
(
s
*
adminServiceImpl
)
ListRedeemCodes
(
ctx
context
.
Context
,
page
,
pageSize
int
,
codeType
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
RedeemCode
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
}
codes
,
result
,
err
:=
s
.
redeemCodeRepo
.
ListWithFilters
(
ctx
,
params
,
codeType
,
status
,
search
)
if
err
!=
nil
{
return
nil
,
0
,
err
...
...
backend/internal/service/admin_service_group_test.go
View file @
5f8e60a1
...
...
@@ -120,6 +120,22 @@ func (s *groupRepoStubForAdmin) UpdateSortOrders(_ context.Context, _ []GroupSor
return
nil
}
func
TestAdminService_ListGroups_PassesSortParams
(
t
*
testing
.
T
)
{
repo
:=
&
groupRepoStubForAdmin
{
listWithFiltersGroups
:
[]
Group
{{
ID
:
1
,
Name
:
"g1"
}},
}
svc
:=
&
adminServiceImpl
{
groupRepo
:
repo
}
_
,
_
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
3
,
25
,
PlatformOpenAI
,
StatusActive
,
"needle"
,
nil
,
"account_count"
,
"ASC"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
3
,
PageSize
:
25
,
SortBy
:
"account_count"
,
SortOrder
:
"ASC"
,
},
repo
.
listWithFiltersParams
)
}
// TestAdminService_CreateGroup_WithImagePricing 测试创建分组时 ImagePrice 字段正确传递
func
TestAdminService_CreateGroup_WithImagePricing
(
t
*
testing
.
T
)
{
repo
:=
&
groupRepoStubForAdmin
{}
...
...
@@ -258,7 +274,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) {
}
svc
:=
&
adminServiceImpl
{
groupRepo
:
repo
}
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
1
,
20
,
""
,
""
,
"alpha"
,
nil
)
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
1
,
20
,
""
,
""
,
"alpha"
,
nil
,
""
,
""
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
1
),
total
)
require
.
Equal
(
t
,
[]
Group
{{
ID
:
1
,
Name
:
"alpha"
}},
groups
)
...
...
@@ -276,7 +292,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) {
}
svc
:=
&
adminServiceImpl
{
groupRepo
:
repo
}
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
2
,
10
,
""
,
""
,
""
,
nil
)
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
2
,
10
,
""
,
""
,
""
,
nil
,
""
,
""
)
require
.
NoError
(
t
,
err
)
require
.
Empty
(
t
,
groups
)
require
.
Equal
(
t
,
int64
(
0
),
total
)
...
...
@@ -295,7 +311,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) {
}
svc
:=
&
adminServiceImpl
{
groupRepo
:
repo
}
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
3
,
50
,
PlatformAntigravity
,
StatusActive
,
"beta"
,
&
isExclusive
)
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
3
,
50
,
PlatformAntigravity
,
StatusActive
,
"beta"
,
&
isExclusive
,
""
,
""
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
42
),
total
)
require
.
Equal
(
t
,
[]
Group
{{
ID
:
2
,
Name
:
"beta"
}},
groups
)
...
...
backend/internal/service/admin_service_list_users_test.go
View file @
5f8e60a1
...
...
@@ -13,11 +13,13 @@ import (
type
userRepoStubForListUsers
struct
{
userRepoStub
users
[]
User
err
error
users
[]
User
err
error
listWithFiltersParams
pagination
.
PaginationParams
}
func
(
s
*
userRepoStubForListUsers
)
ListWithFilters
(
_
context
.
Context
,
params
pagination
.
PaginationParams
,
_
UserListFilters
)
([]
User
,
*
pagination
.
PaginationResult
,
error
)
{
s
.
listWithFiltersParams
=
params
if
s
.
err
!=
nil
{
return
nil
,
nil
,
s
.
err
}
...
...
@@ -103,7 +105,7 @@ func TestAdminService_ListUsers_BatchRateFallbackToSingle(t *testing.T) {
userGroupRateRepo
:
rateRepo
,
}
users
,
total
,
err
:=
svc
.
ListUsers
(
context
.
Background
(),
1
,
20
,
UserListFilters
{})
users
,
total
,
err
:=
svc
.
ListUsers
(
context
.
Background
(),
1
,
20
,
UserListFilters
{}
,
""
,
""
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
2
),
total
)
require
.
Len
(
t
,
users
,
2
)
...
...
@@ -112,3 +114,19 @@ func TestAdminService_ListUsers_BatchRateFallbackToSingle(t *testing.T) {
require
.
Equal
(
t
,
1.1
,
users
[
0
]
.
GroupRates
[
11
])
require
.
Equal
(
t
,
2.2
,
users
[
1
]
.
GroupRates
[
22
])
}
func
TestAdminService_ListUsers_PassesSortParams
(
t
*
testing
.
T
)
{
userRepo
:=
&
userRepoStubForListUsers
{
users
:
[]
User
{{
ID
:
1
,
Email
:
"a@example.com"
}},
}
svc
:=
&
adminServiceImpl
{
userRepo
:
userRepo
}
_
,
_
,
err
:=
svc
.
ListUsers
(
context
.
Background
(),
2
,
50
,
UserListFilters
{},
"email"
,
"ASC"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
2
,
PageSize
:
50
,
SortBy
:
"email"
,
SortOrder
:
"ASC"
,
},
userRepo
.
listWithFiltersParams
)
}
backend/internal/service/admin_service_search_test.go
View file @
5f8e60a1
...
...
@@ -170,13 +170,13 @@ func TestAdminService_ListAccounts_WithSearch(t *testing.T) {
}
svc
:=
&
adminServiceImpl
{
accountRepo
:
repo
}
accounts
,
total
,
err
:=
svc
.
ListAccounts
(
context
.
Background
(),
1
,
20
,
PlatformGemini
,
AccountTypeOAuth
,
StatusActive
,
"acc"
,
0
,
""
)
accounts
,
total
,
err
:=
svc
.
ListAccounts
(
context
.
Background
(),
1
,
20
,
PlatformGemini
,
AccountTypeOAuth
,
StatusActive
,
"acc"
,
0
,
""
,
"name"
,
"ASC"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
10
),
total
)
require
.
Equal
(
t
,
[]
Account
{{
ID
:
1
,
Name
:
"acc"
}},
accounts
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
20
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
20
,
SortBy
:
"name"
,
SortOrder
:
"ASC"
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
PlatformGemini
,
repo
.
listWithFiltersPlatform
)
require
.
Equal
(
t
,
AccountTypeOAuth
,
repo
.
listWithFiltersType
)
require
.
Equal
(
t
,
StatusActive
,
repo
.
listWithFiltersStatus
)
...
...
@@ -192,7 +192,7 @@ func TestAdminService_ListAccounts_WithPrivacyMode(t *testing.T) {
}
svc
:=
&
adminServiceImpl
{
accountRepo
:
repo
}
accounts
,
total
,
err
:=
svc
.
ListAccounts
(
context
.
Background
(),
1
,
20
,
PlatformOpenAI
,
AccountTypeOAuth
,
StatusActive
,
"acc2"
,
0
,
PrivacyModeCFBlocked
)
accounts
,
total
,
err
:=
svc
.
ListAccounts
(
context
.
Background
(),
1
,
20
,
PlatformOpenAI
,
AccountTypeOAuth
,
StatusActive
,
"acc2"
,
0
,
PrivacyModeCFBlocked
,
""
,
""
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
1
),
total
)
require
.
Equal
(
t
,
[]
Account
{{
ID
:
2
,
Name
:
"acc2"
}},
accounts
)
...
...
@@ -208,13 +208,13 @@ func TestAdminService_ListProxies_WithSearch(t *testing.T) {
}
svc
:=
&
adminServiceImpl
{
proxyRepo
:
repo
}
proxies
,
total
,
err
:=
svc
.
ListProxies
(
context
.
Background
(),
3
,
50
,
"http"
,
StatusActive
,
"p1"
)
proxies
,
total
,
err
:=
svc
.
ListProxies
(
context
.
Background
(),
3
,
50
,
"http"
,
StatusActive
,
"p1"
,
"name"
,
"ASC"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
7
),
total
)
require
.
Equal
(
t
,
[]
Proxy
{{
ID
:
2
,
Name
:
"p1"
}},
proxies
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
3
,
PageSize
:
50
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
3
,
PageSize
:
50
,
SortBy
:
"name"
,
SortOrder
:
"ASC"
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
"http"
,
repo
.
listWithFiltersProtocol
)
require
.
Equal
(
t
,
StatusActive
,
repo
.
listWithFiltersStatus
)
require
.
Equal
(
t
,
"p1"
,
repo
.
listWithFiltersSearch
)
...
...
@@ -229,13 +229,13 @@ func TestAdminService_ListProxiesWithAccountCount_WithSearch(t *testing.T) {
}
svc
:=
&
adminServiceImpl
{
proxyRepo
:
repo
}
proxies
,
total
,
err
:=
svc
.
ListProxiesWithAccountCount
(
context
.
Background
(),
2
,
10
,
"socks5"
,
StatusDisabled
,
"p2"
)
proxies
,
total
,
err
:=
svc
.
ListProxiesWithAccountCount
(
context
.
Background
(),
2
,
10
,
"socks5"
,
StatusDisabled
,
"p2"
,
"account_count"
,
"DESC"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
9
),
total
)
require
.
Equal
(
t
,
[]
ProxyWithAccountCount
{{
Proxy
:
Proxy
{
ID
:
3
,
Name
:
"p2"
},
AccountCount
:
5
}},
proxies
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersAndAccountCountCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
2
,
PageSize
:
10
},
repo
.
listWithFiltersAndAccountCountParams
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
2
,
PageSize
:
10
,
SortBy
:
"account_count"
,
SortOrder
:
"DESC"
},
repo
.
listWithFiltersAndAccountCountParams
)
require
.
Equal
(
t
,
"socks5"
,
repo
.
listWithFiltersAndAccountCountProtocol
)
require
.
Equal
(
t
,
StatusDisabled
,
repo
.
listWithFiltersAndAccountCountStatus
)
require
.
Equal
(
t
,
"p2"
,
repo
.
listWithFiltersAndAccountCountSearch
)
...
...
@@ -250,13 +250,13 @@ func TestAdminService_ListRedeemCodes_WithSearch(t *testing.T) {
}
svc
:=
&
adminServiceImpl
{
redeemCodeRepo
:
repo
}
codes
,
total
,
err
:=
svc
.
ListRedeemCodes
(
context
.
Background
(),
1
,
20
,
RedeemTypeBalance
,
StatusUnused
,
"ABC"
)
codes
,
total
,
err
:=
svc
.
ListRedeemCodes
(
context
.
Background
(),
1
,
20
,
RedeemTypeBalance
,
StatusUnused
,
"ABC"
,
"value"
,
"ASC"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
3
),
total
)
require
.
Equal
(
t
,
[]
RedeemCode
{{
ID
:
4
,
Code
:
"ABC"
}},
codes
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
20
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
20
,
SortBy
:
"value"
,
SortOrder
:
"ASC"
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
RedeemTypeBalance
,
repo
.
listWithFiltersType
)
require
.
Equal
(
t
,
StatusUnused
,
repo
.
listWithFiltersStatus
)
require
.
Equal
(
t
,
"ABC"
,
repo
.
listWithFiltersSearch
)
...
...
backend/internal/service/domain_constants.go
View file @
5f8e60a1
...
...
@@ -116,6 +116,8 @@ const (
SettingKeyHideCcsImportButton
=
"hide_ccs_import_button"
// 是否隐藏 API Keys 页面的导入 CCS 按钮
SettingKeyPurchaseSubscriptionEnabled
=
"purchase_subscription_enabled"
// 是否展示"购买订阅"页面入口
SettingKeyPurchaseSubscriptionURL
=
"purchase_subscription_url"
// "购买订阅"页面 URL(作为 iframe src)
SettingKeyTableDefaultPageSize
=
"table_default_page_size"
// 表格默认每页条数
SettingKeyTablePageSizeOptions
=
"table_page_size_options"
// 表格可选每页条数(JSON 数组)
SettingKeyCustomMenuItems
=
"custom_menu_items"
// 自定义菜单项(JSON 数组)
SettingKeyCustomEndpoints
=
"custom_endpoints"
// 自定义端点列表(JSON 数组)
...
...
backend/internal/service/openai_ws_ratelimit_signal_test.go
View file @
5f8e60a1
...
...
@@ -492,7 +492,7 @@ func TestAdminService_ListAccounts_ExhaustedCodexExtraReturnsRateLimitedAccount(
}
svc
:=
&
adminServiceImpl
{
accountRepo
:
repo
}
accounts
,
total
,
err
:=
svc
.
ListAccounts
(
context
.
Background
(),
1
,
20
,
PlatformOpenAI
,
AccountTypeOAuth
,
""
,
""
,
0
,
""
)
accounts
,
total
,
err
:=
svc
.
ListAccounts
(
context
.
Background
(),
1
,
20
,
PlatformOpenAI
,
AccountTypeOAuth
,
""
,
""
,
0
,
""
,
""
,
""
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
1
),
total
)
require
.
Len
(
t
,
accounts
,
1
)
...
...
frontend/src/api/admin/accounts.ts
View file @
5f8e60a1
...
...
@@ -38,6 +38,8 @@ export async function list(
search
?:
string
privacy_mode
?:
string
lite
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
...
...
@@ -71,6 +73,8 @@ export async function listWithEtag(
search
?:
string
privacy_mode
?:
string
lite
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
...
...
@@ -500,7 +504,11 @@ export async function exportData(options?: {
platform
?:
string
type
?:
string
status
?:
string
group
?:
string
privacy_mode
?:
string
search
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
}
includeProxies
?:
boolean
}):
Promise
<
AdminDataPayload
>
{
...
...
@@ -508,11 +516,15 @@ export async function exportData(options?: {
if
(
options
?.
ids
&&
options
.
ids
.
length
>
0
)
{
params
.
ids
=
options
.
ids
.
join
(
'
,
'
)
}
else
if
(
options
?.
filters
)
{
const
{
platform
,
type
,
status
,
search
}
=
options
.
filters
const
{
platform
,
type
,
status
,
group
,
privacy_mode
,
search
,
sort_by
,
sort_order
}
=
options
.
filters
if
(
platform
)
params
.
platform
=
platform
if
(
type
)
params
.
type
=
type
if
(
status
)
params
.
status
=
status
if
(
group
)
params
.
group
=
group
if
(
privacy_mode
)
params
.
privacy_mode
=
privacy_mode
if
(
search
)
params
.
search
=
search
if
(
sort_by
)
params
.
sort_by
=
sort_by
if
(
sort_order
)
params
.
sort_order
=
sort_order
}
if
(
options
?.
includeProxies
===
false
)
{
params
.
include_proxies
=
'
false
'
...
...
frontend/src/api/admin/announcements.ts
View file @
5f8e60a1
...
...
@@ -17,10 +17,16 @@ export async function list(
filters
?:
{
status
?:
string
search
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
}
):
Promise
<
BasePaginationResponse
<
Announcement
>>
{
const
{
data
}
=
await
apiClient
.
get
<
BasePaginationResponse
<
Announcement
>>
(
'
/admin/announcements
'
,
{
params
:
{
page
,
page_size
:
pageSize
,
...
filters
}
params
:
{
page
,
page_size
:
pageSize
,
...
filters
},
signal
:
options
?.
signal
})
return
data
}
...
...
@@ -49,11 +55,21 @@ export async function getReadStatus(
id
:
number
,
page
:
number
=
1
,
pageSize
:
number
=
20
,
search
:
string
=
''
filters
?:
{
search
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
}
):
Promise
<
BasePaginationResponse
<
AnnouncementUserReadStatus
>>
{
const
{
data
}
=
await
apiClient
.
get
<
BasePaginationResponse
<
AnnouncementUserReadStatus
>>
(
`/admin/announcements/
${
id
}
/read-status`
,
{
params
:
{
page
,
page_size
:
pageSize
,
search
}
}
{
params
:
{
page
,
page_size
:
pageSize
,
...
filters
},
signal
:
options
?.
signal
}
)
return
data
}
...
...
@@ -68,4 +84,3 @@ const announcementsAPI = {
}
export
default
announcementsAPI
frontend/src/api/admin/channels.ts
View file @
5f8e60a1
...
...
@@ -83,6 +83,8 @@ export async function list(
filters
?:
{
status
?:
string
search
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
}
):
Promise
<
PaginatedResponse
<
Channel
>>
{
...
...
frontend/src/api/admin/groups.ts
View file @
5f8e60a1
...
...
@@ -27,6 +27,8 @@ export async function list(
status
?:
'
active
'
|
'
inactive
'
is_exclusive
?:
boolean
search
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
...
...
frontend/src/api/admin/promo.ts
View file @
5f8e60a1
...
...
@@ -17,10 +17,16 @@ export async function list(
filters
?:
{
status
?:
string
search
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
}
):
Promise
<
BasePaginationResponse
<
PromoCode
>>
{
const
{
data
}
=
await
apiClient
.
get
<
BasePaginationResponse
<
PromoCode
>>
(
'
/admin/promo-codes
'
,
{
params
:
{
page
,
page_size
:
pageSize
,
...
filters
}
params
:
{
page
,
page_size
:
pageSize
,
...
filters
},
signal
:
options
?.
signal
})
return
data
}
...
...
frontend/src/api/admin/proxies.ts
View file @
5f8e60a1
...
...
@@ -29,6 +29,8 @@ export async function list(
protocol
?:
string
status
?:
'
active
'
|
'
inactive
'
search
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
...
...
@@ -227,16 +229,20 @@ export async function exportData(options?: {
protocol
?:
string
status
?:
'
active
'
|
'
inactive
'
search
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
}
}):
Promise
<
AdminDataPayload
>
{
const
params
:
Record
<
string
,
string
>
=
{}
if
(
options
?.
ids
&&
options
.
ids
.
length
>
0
)
{
params
.
ids
=
options
.
ids
.
join
(
'
,
'
)
}
else
if
(
options
?.
filters
)
{
const
{
protocol
,
status
,
search
}
=
options
.
filters
const
{
protocol
,
status
,
search
,
sort_by
,
sort_order
}
=
options
.
filters
if
(
protocol
)
params
.
protocol
=
protocol
if
(
status
)
params
.
status
=
status
if
(
search
)
params
.
search
=
search
if
(
sort_by
)
params
.
sort_by
=
sort_by
if
(
sort_order
)
params
.
sort_order
=
sort_order
}
const
{
data
}
=
await
apiClient
.
get
<
AdminDataPayload
>
(
'
/admin/proxies/data
'
,
{
params
})
return
data
...
...
frontend/src/api/admin/usage.ts
View file @
5f8e60a1
...
...
@@ -81,6 +81,8 @@ export interface AdminUsageQueryParams extends UsageQueryParams {
user_id
?:
number
exact_total
?:
boolean
billing_mode
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
}
// ==================== API Functions ====================
...
...
frontend/src/api/admin/users.ts
View file @
5f8e60a1
...
...
@@ -24,6 +24,8 @@ export async function list(
group_name
?:
string
// fuzzy filter by allowed group name
attributes
?:
Record
<
number
,
string
>
// attributeId -> value
include_subscriptions
?:
boolean
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
...
...
@@ -37,7 +39,9 @@ export async function list(
role
:
filters
?.
role
,
search
:
filters
?.
search
,
group_name
:
filters
?.
group_name
,
include_subscriptions
:
filters
?.
include_subscriptions
include_subscriptions
:
filters
?.
include_subscriptions
,
sort_by
:
filters
?.
sort_by
,
sort_order
:
filters
?.
sort_order
}
// Add attribute filters as attr[id]=value
...
...
frontend/src/api/keys.ts
View file @
5f8e60a1
...
...
@@ -17,7 +17,13 @@ import type { ApiKey, CreateApiKeyRequest, UpdateApiKeyRequest, PaginatedRespons
export
async
function
list
(
page
:
number
=
1
,
pageSize
:
number
=
10
,
filters
?:
{
search
?:
string
;
status
?:
string
;
group_id
?:
number
|
string
},
filters
?:
{
search
?:
string
status
?:
string
group_id
?:
number
|
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
options
?:
{
signal
?:
AbortSignal
}
...
...
frontend/src/api/usage.ts
View file @
5f8e60a1
...
...
@@ -91,7 +91,7 @@ export async function list(
* @returns Paginated list of usage logs
*/
export
async
function
query
(
params
:
UsageQueryParams
,
params
:
UsageQueryParams
&
{
sort_by
?:
string
;
sort_order
?:
'
asc
'
|
'
desc
'
}
,
config
:
{
signal
?:
AbortSignal
}
=
{}
):
Promise
<
PaginatedResponse
<
UsageLog
>>
{
const
{
data
}
=
await
apiClient
.
get
<
PaginatedResponse
<
UsageLog
>>
(
'
/usage
'
,
{
...
...
frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue
View file @
5f8e60a1
...
...
@@ -21,7 +21,15 @@
</button>
</div>
<DataTable
:columns=
"columns"
:data=
"items"
:loading=
"loading"
>
<DataTable
:columns=
"columns"
:data=
"items"
:loading=
"loading"
:server-side-sort=
"true"
default-sort-key=
"email"
default-sort-order=
"asc"
@
sort=
"handleSort"
>
<template
#cell-email
="
{ value }">
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
</
template
>
...
...
@@ -62,7 +70,7 @@
</template>
<
script
setup
lang=
"ts"
>
import
{
computed
,
on
M
ounted
,
reactive
,
ref
,
watch
}
from
'
vue
'
import
{
computed
,
on
Unm
ounted
,
reactive
,
ref
,
watch
}
from
'
vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useAppStore
}
from
'
@/stores/app
'
import
{
adminAPI
}
from
'
@/api/admin
'
...
...
@@ -98,23 +106,54 @@ const pagination = reactive({
pages
:
0
})
const
sortState
=
reactive
({
sort_by
:
'
email
'
,
sort_order
:
'
asc
'
as
'
asc
'
|
'
desc
'
})
const
items
=
ref
<
AnnouncementUserReadStatus
[]
>
([])
const
columns
=
computed
<
Column
[]
>
(()
=>
[
{
key
:
'
email
'
,
label
:
t
(
'
common.email
'
)
},
{
key
:
'
username
'
,
label
:
t
(
'
admin.users.columns.username
'
)
},
{
key
:
'
balance
'
,
label
:
t
(
'
common.balance
'
)
},
{
key
:
'
email
'
,
label
:
t
(
'
common.email
'
)
,
sortable
:
true
},
{
key
:
'
username
'
,
label
:
t
(
'
admin.users.columns.username
'
)
,
sortable
:
true
},
{
key
:
'
balance
'
,
label
:
t
(
'
common.balance
'
)
,
sortable
:
true
},
{
key
:
'
eligible
'
,
label
:
t
(
'
admin.announcements.eligible
'
)
},
{
key
:
'
read_at
'
,
label
:
t
(
'
admin.announcements.readAt
'
)
}
])
let
currentController
:
AbortController
|
null
=
null
let
searchDebounceTimer
:
number
|
null
=
null
function
resetDialogState
()
{
loading
.
value
=
false
search
.
value
=
''
items
.
value
=
[]
pagination
.
page
=
1
pagination
.
total
=
0
pagination
.
pages
=
0
sortState
.
sort_by
=
'
email
'
sortState
.
sort_order
=
'
asc
'
}
function
cancelPendingLoad
(
resetState
=
false
)
{
if
(
searchDebounceTimer
)
{
window
.
clearTimeout
(
searchDebounceTimer
)
searchDebounceTimer
=
null
}
currentController
?.
abort
()
currentController
=
null
if
(
resetState
)
{
resetDialogState
()
}
}
async
function
load
()
{
if
(
!
props
.
show
||
!
props
.
announcementId
)
return
if
(
currentController
)
currentController
.
abort
()
currentController
=
new
AbortController
()
currentController
?.
abort
()
const
requestController
=
new
AbortController
()
currentController
=
requestController
const
{
signal
}
=
requestController
try
{
loading
.
value
=
true
...
...
@@ -122,20 +161,37 @@ async function load() {
props
.
announcementId
,
pagination
.
page
,
pagination
.
page_size
,
search
.
value
{
search
:
search
.
value
,
sort_by
:
sortState
.
sort_by
,
sort_order
:
sortState
.
sort_order
},
{
signal
}
)
if
(
signal
.
aborted
||
currentController
!==
requestController
)
return
items
.
value
=
res
.
items
pagination
.
total
=
res
.
total
pagination
.
pages
=
res
.
pages
pagination
.
page
=
res
.
page
pagination
.
page_size
=
res
.
page_size
}
catch
(
error
:
any
)
{
if
(
currentController
.
signal
.
aborted
||
error
?.
name
===
'
AbortError
'
)
return
if
(
signal
.
aborted
||
currentController
!==
requestController
||
error
?.
name
===
'
AbortError
'
||
error
?.
code
===
'
ERR_CANCELED
'
)
{
return
}
console
.
error
(
'
Failed to load read status:
'
,
error
)
appStore
.
showError
(
error
.
response
?.
data
?.
detail
||
t
(
'
admin.announcements.failedToLoadReadStatus
'
))
}
finally
{
loading
.
value
=
false
if
(
currentController
===
requestController
)
{
loading
.
value
=
false
currentController
=
null
}
}
}
...
...
@@ -150,7 +206,13 @@ function handlePageSizeChange(pageSize: number) {
load
()
}
let
searchDebounceTimer
:
number
|
null
=
null
function
handleSort
(
key
:
string
,
order
:
'
asc
'
|
'
desc
'
)
{
sortState
.
sort_by
=
key
sortState
.
sort_order
=
order
pagination
.
page
=
1
load
()
}
function
handleSearch
()
{
if
(
searchDebounceTimer
)
window
.
clearTimeout
(
searchDebounceTimer
)
searchDebounceTimer
=
window
.
setTimeout
(()
=>
{
...
...
@@ -160,13 +222,17 @@ function handleSearch() {
}
function
handleClose
()
{
cancelPendingLoad
(
true
)
emit
(
'
close
'
)
}
watch
(
()
=>
props
.
show
,
(
v
)
=>
{
if
(
!
v
)
return
if
(
!
v
)
{
cancelPendingLoad
(
true
)
return
}
pagination
.
page
=
1
load
()
}
...
...
@@ -181,7 +247,7 @@ watch(
}
)
on
M
ounted
(()
=>
{
// noop
on
Unm
ounted
(()
=>
{
cancelPendingLoad
()
})
</
script
>
frontend/src/components/admin/announcements/__tests__/AnnouncementReadStatusDialog.spec.ts
0 → 100644
View file @
5f8e60a1
import
{
describe
,
it
,
expect
,
vi
,
beforeEach
}
from
'
vitest
'
import
{
flushPromises
,
mount
}
from
'
@vue/test-utils
'
import
AnnouncementReadStatusDialog
from
'
../AnnouncementReadStatusDialog.vue
'
const
{
getReadStatus
,
showError
}
=
vi
.
hoisted
(()
=>
({
getReadStatus
:
vi
.
fn
(),
showError
:
vi
.
fn
(),
}))
vi
.
mock
(
'
@/api/admin
'
,
()
=>
({
adminAPI
:
{
announcements
:
{
getReadStatus
,
},
},
}))
vi
.
mock
(
'
@/stores/app
'
,
()
=>
({
useAppStore
:
()
=>
({
showError
,
}),
}))
vi
.
mock
(
'
vue-i18n
'
,
async
()
=>
{
const
actual
=
await
vi
.
importActual
<
typeof
import
(
'
vue-i18n
'
)
>
(
'
vue-i18n
'
)
return
{
...
actual
,
useI18n
:
()
=>
({
t
:
(
key
:
string
)
=>
key
,
}),
}
})
vi
.
mock
(
'
@/composables/usePersistedPageSize
'
,
()
=>
({
getPersistedPageSize
:
()
=>
20
,
}))
const
BaseDialogStub
=
{
props
:
[
'
show
'
,
'
title
'
,
'
width
'
],
emits
:
[
'
close
'
],
template
:
'
<div><slot /><slot name="footer" /></div>
'
,
}
describe
(
'
AnnouncementReadStatusDialog
'
,
()
=>
{
beforeEach
(()
=>
{
getReadStatus
.
mockReset
()
showError
.
mockReset
()
vi
.
useFakeTimers
()
})
it
(
'
closes by aborting active requests and clearing debounced reloads
'
,
async
()
=>
{
let
activeSignal
:
AbortSignal
|
undefined
getReadStatus
.
mockImplementation
(
async
(...
args
:
any
[])
=>
{
activeSignal
=
args
[
4
]?.
signal
return
new
Promise
(()
=>
{})
})
const
wrapper
=
mount
(
AnnouncementReadStatusDialog
,
{
props
:
{
show
:
false
,
announcementId
:
1
,
},
global
:
{
stubs
:
{
BaseDialog
:
BaseDialogStub
,
DataTable
:
true
,
Pagination
:
true
,
Icon
:
true
,
},
},
})
await
wrapper
.
setProps
({
show
:
true
})
await
flushPromises
()
expect
(
getReadStatus
).
toHaveBeenCalledTimes
(
1
)
expect
(
activeSignal
?.
aborted
).
toBe
(
false
)
const
setupState
=
(
wrapper
.
vm
as
any
).
$
?.
setupState
setupState
.
search
=
'
alice
'
setupState
.
handleSearch
()
setupState
.
handleClose
()
await
flushPromises
()
expect
(
activeSignal
?.
aborted
).
toBe
(
true
)
expect
(
wrapper
.
emitted
(
'
close
'
)).
toHaveLength
(
1
)
vi
.
advanceTimersByTime
(
350
)
await
flushPromises
()
expect
(
getReadStatus
).
toHaveBeenCalledTimes
(
1
)
})
})
frontend/src/components/admin/group/GroupRateMultipliersModal.vue
View file @
5f8e60a1
...
...
@@ -196,7 +196,6 @@
:total=
"localEntries.length"
:page=
"currentPage"
:page-size=
"pageSize"
:page-size-options=
"[10, 20, 50]"
@
update:page=
"currentPage = $event"
@
update:pageSize=
"handlePageSizeChange"
/>
...
...
frontend/src/components/admin/usage/UsageTable.vue
View file @
5f8e60a1
<
template
>
<div
class=
"card overflow-hidden"
>
<div
class=
"overflow-auto"
>
<DataTable
:columns=
"columns"
:data=
"data"
:loading=
"loading"
>
<DataTable
:columns=
"columns"
:data=
"data"
:loading=
"loading"
:server-side-sort=
"serverSideSort"
:default-sort-key=
"defaultSortKey"
:default-sort-order=
"defaultSortOrder"
@
sort=
"(key, order) => $emit('sort', key, order)"
>
<template
#cell-user
="
{ row }">
<div
class=
"text-sm"
>
<button
...
...
@@ -334,9 +342,27 @@ import DataTable from '@/components/common/DataTable.vue'
import
EmptyState
from
'
@/components/common/EmptyState.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
type
{
AdminUsageLog
}
from
'
@/types
'
import
type
{
Column
}
from
'
@/components/common/types
'
interface
Props
{
data
:
AdminUsageLog
[]
loading
?:
boolean
columns
:
Column
[]
serverSideSort
?:
boolean
defaultSortKey
?:
string
defaultSortOrder
?:
'
asc
'
|
'
desc
'
}
defineProps
([
'
data
'
,
'
loading
'
,
'
columns
'
])
defineEmits
([
'
userClick
'
])
withDefaults
(
defineProps
<
Props
>
(),
{
loading
:
false
,
serverSideSort
:
false
,
defaultSortKey
:
''
,
defaultSortOrder
:
'
asc
'
})
defineEmits
<
{
userClick
:
[
userID
:
number
,
email
?:
string
]
sort
:
[
key
:
string
,
order
:
'
asc
'
|
'
desc
'
]
}
>
()
const
{
t
}
=
useI18n
()
// Tooltip state - cost
...
...
Prev
1
2
3
4
Next
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