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
dc447cce
Unverified
Commit
dc447cce
authored
Mar 19, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 19, 2026
Browse files
Merge pull request #1153 from hging/main
feat: add ungrouped filter to account
parents
7ec29638
8027531d
Changes
9
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/account_handler.go
View file @
dc447cce
...
...
@@ -165,6 +165,8 @@ type AccountWithConcurrency struct {
CurrentRPM
*
int
`json:"current_rpm,omitempty"`
// 当前分钟 RPM 计数
}
const
accountListGroupUngroupedQueryValue
=
"ungrouped"
func
(
h
*
AccountHandler
)
buildAccountResponseWithRuntime
(
ctx
context
.
Context
,
account
*
service
.
Account
)
AccountWithConcurrency
{
item
:=
AccountWithConcurrency
{
Account
:
dto
.
AccountFromService
(
account
),
...
...
@@ -226,7 +228,20 @@ func (h *AccountHandler) List(c *gin.Context) {
var
groupID
int64
if
groupIDStr
:=
c
.
Query
(
"group"
);
groupIDStr
!=
""
{
groupID
,
_
=
strconv
.
ParseInt
(
groupIDStr
,
10
,
64
)
if
groupIDStr
==
accountListGroupUngroupedQueryValue
{
groupID
=
service
.
AccountListGroupUngrouped
}
else
{
parsedGroupID
,
parseErr
:=
strconv
.
ParseInt
(
groupIDStr
,
10
,
64
)
if
parseErr
!=
nil
{
response
.
ErrorFrom
(
c
,
infraerrors
.
BadRequest
(
"INVALID_GROUP_FILTER"
,
"invalid group filter"
))
return
}
if
parsedGroupID
<
0
{
response
.
ErrorFrom
(
c
,
infraerrors
.
BadRequest
(
"INVALID_GROUP_FILTER"
,
"invalid group filter"
))
return
}
groupID
=
parsedGroupID
}
}
accounts
,
total
,
err
:=
h
.
adminService
.
ListAccounts
(
c
.
Request
.
Context
(),
page
,
pageSize
,
platform
,
accountType
,
status
,
search
,
groupID
)
...
...
backend/internal/repository/account_repo.go
View file @
dc447cce
...
...
@@ -474,7 +474,9 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
if
search
!=
""
{
q
=
q
.
Where
(
dbaccount
.
NameContainsFold
(
search
))
}
if
groupID
>
0
{
if
groupID
==
service
.
AccountListGroupUngrouped
{
q
=
q
.
Where
(
dbaccount
.
Not
(
dbaccount
.
HasAccountGroups
()))
}
else
if
groupID
>
0
{
q
=
q
.
Where
(
dbaccount
.
HasAccountGroupsWith
(
dbaccountgroup
.
GroupIDEQ
(
groupID
)))
}
...
...
backend/internal/repository/account_repo_integration_test.go
View file @
dc447cce
...
...
@@ -214,6 +214,7 @@ func (s *AccountRepoSuite) TestListWithFilters() {
accType
string
status
string
search
string
groupID
int64
wantCount
int
validate
func
(
accounts
[]
service
.
Account
)
}{
...
...
@@ -265,6 +266,21 @@ func (s *AccountRepoSuite) TestListWithFilters() {
s
.
Require
()
.
Contains
(
accounts
[
0
]
.
Name
,
"alpha"
)
},
},
{
name
:
"filter_by_ungrouped"
,
setup
:
func
(
client
*
dbent
.
Client
)
{
group
:=
mustCreateGroup
(
s
.
T
(),
client
,
&
service
.
Group
{
Name
:
"g-ungrouped"
})
grouped
:=
mustCreateAccount
(
s
.
T
(),
client
,
&
service
.
Account
{
Name
:
"grouped-account"
})
mustCreateAccount
(
s
.
T
(),
client
,
&
service
.
Account
{
Name
:
"ungrouped-account"
})
mustBindAccountToGroup
(
s
.
T
(),
client
,
grouped
.
ID
,
group
.
ID
,
1
)
},
groupID
:
service
.
AccountListGroupUngrouped
,
wantCount
:
1
,
validate
:
func
(
accounts
[]
service
.
Account
)
{
s
.
Require
()
.
Equal
(
"ungrouped-account"
,
accounts
[
0
]
.
Name
)
s
.
Require
()
.
Empty
(
accounts
[
0
]
.
GroupIDs
)
},
},
}
for
_
,
tt
:=
range
tests
{
...
...
@@ -277,7 +293,7 @@ func (s *AccountRepoSuite) TestListWithFilters() {
tt
.
setup
(
client
)
accounts
,
_
,
err
:=
repo
.
ListWithFilters
(
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
tt
.
platform
,
tt
.
accType
,
tt
.
status
,
tt
.
search
,
0
)
accounts
,
_
,
err
:=
repo
.
ListWithFilters
(
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
tt
.
platform
,
tt
.
accType
,
tt
.
status
,
tt
.
search
,
tt
.
groupID
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Len
(
accounts
,
tt
.
wantCount
)
if
tt
.
validate
!=
nil
{
...
...
backend/internal/service/account_service.go
View file @
dc447cce
...
...
@@ -14,6 +14,8 @@ var (
ErrAccountNilInput
=
infraerrors
.
BadRequest
(
"ACCOUNT_NIL_INPUT"
,
"account input cannot be nil"
)
)
const
AccountListGroupUngrouped
int64
=
-
1
type
AccountRepository
interface
{
Create
(
ctx
context
.
Context
,
account
*
Account
)
error
GetByID
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
...
...
frontend/src/api/admin/accounts.ts
View file @
dc447cce
...
...
@@ -66,6 +66,7 @@ export async function listWithEtag(
platform
?:
string
type
?:
string
status
?:
string
group
?:
string
search
?:
string
lite
?:
string
},
...
...
frontend/src/components/admin/account/AccountTableFilters.vue
View file @
dc447cce
...
...
@@ -26,5 +26,9 @@ const updateGroup = (value: string | number | boolean | null) => { emit('update:
const
pOpts
=
computed
(()
=>
[{
value
:
''
,
label
:
t
(
'
admin.accounts.allPlatforms
'
)
},
{
value
:
'
anthropic
'
,
label
:
'
Anthropic
'
},
{
value
:
'
openai
'
,
label
:
'
OpenAI
'
},
{
value
:
'
gemini
'
,
label
:
'
Gemini
'
},
{
value
:
'
antigravity
'
,
label
:
'
Antigravity
'
},
{
value
:
'
sora
'
,
label
:
'
Sora
'
}])
const
tOpts
=
computed
(()
=>
[{
value
:
''
,
label
:
t
(
'
admin.accounts.allTypes
'
)
},
{
value
:
'
oauth
'
,
label
:
t
(
'
admin.accounts.oauthType
'
)
},
{
value
:
'
setup-token
'
,
label
:
t
(
'
admin.accounts.setupToken
'
)
},
{
value
:
'
apikey
'
,
label
:
t
(
'
admin.accounts.apiKey
'
)
},
{
value
:
'
bedrock
'
,
label
:
'
AWS Bedrock
'
}])
const
sOpts
=
computed
(()
=>
[{
value
:
''
,
label
:
t
(
'
admin.accounts.allStatus
'
)
},
{
value
:
'
active
'
,
label
:
t
(
'
admin.accounts.status.active
'
)
},
{
value
:
'
inactive
'
,
label
:
t
(
'
admin.accounts.status.inactive
'
)
},
{
value
:
'
error
'
,
label
:
t
(
'
admin.accounts.status.error
'
)
},
{
value
:
'
rate_limited
'
,
label
:
t
(
'
admin.accounts.status.rateLimited
'
)
},
{
value
:
'
temp_unschedulable
'
,
label
:
t
(
'
admin.accounts.status.tempUnschedulable
'
)
}])
const
gOpts
=
computed
(()
=>
[{
value
:
''
,
label
:
t
(
'
admin.accounts.allGroups
'
)
},
...(
props
.
groups
||
[]).
map
(
g
=>
({
value
:
String
(
g
.
id
),
label
:
g
.
name
}))])
const
gOpts
=
computed
(()
=>
[
{
value
:
''
,
label
:
t
(
'
admin.accounts.allGroups
'
)
},
{
value
:
'
ungrouped
'
,
label
:
t
(
'
admin.accounts.ungroupedGroup
'
)
},
...(
props
.
groups
||
[]).
map
(
g
=>
({
value
:
String
(
g
.
id
),
label
:
g
.
name
}))
])
</
script
>
frontend/src/i18n/locales/en.ts
View file @
dc447cce
...
...
@@ -1883,6 +1883,7 @@ export default {
allTypes
:
'
All Types
'
,
allStatus
:
'
All Status
'
,
allGroups
:
'
All Groups
'
,
ungroupedGroup
:
'
Ungrouped
'
,
oauthType
:
'
OAuth
'
,
setupToken
:
'
Setup Token
'
,
apiKey
:
'
API Key
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
dc447cce
...
...
@@ -1965,6 +1965,7 @@ export default {
allTypes
:
'
全部类型
'
,
allStatus
:
'
全部状态
'
,
allGroups
:
'
全部分组
'
,
ungroupedGroup
:
'
未分配分组
'
,
oauthType
:
'
OAuth
'
,
// Schedulable toggle
schedulable
:
'
参与调度
'
,
...
...
frontend/src/views/admin/AccountsView.vue
View file @
dc447cce
...
...
@@ -758,6 +758,7 @@ const refreshAccountsIncrementally = async () => {
platform
?:
string
type
?:
string
status
?:
string
group
?:
string
search
?:
string
}
,
...
...
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