Skip to content

Commit dc447cc

Browse files
authored
Merge pull request Wei-Shaw#1153 from hging/main
feat: add ungrouped filter to account
2 parents 7ec2963 + 8027531 commit dc447cc

9 files changed

Lines changed: 47 additions & 4 deletions

File tree

backend/internal/handler/admin/account_handler.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ type AccountWithConcurrency struct {
165165
CurrentRPM *int `json:"current_rpm,omitempty"` // 当前分钟 RPM 计数
166166
}
167167

168+
const accountListGroupUngroupedQueryValue = "ungrouped"
169+
168170
func (h *AccountHandler) buildAccountResponseWithRuntime(ctx context.Context, account *service.Account) AccountWithConcurrency {
169171
item := AccountWithConcurrency{
170172
Account: dto.AccountFromService(account),
@@ -226,7 +228,20 @@ func (h *AccountHandler) List(c *gin.Context) {
226228

227229
var groupID int64
228230
if groupIDStr := c.Query("group"); groupIDStr != "" {
229-
groupID, _ = strconv.ParseInt(groupIDStr, 10, 64)
231+
if groupIDStr == accountListGroupUngroupedQueryValue {
232+
groupID = service.AccountListGroupUngrouped
233+
} else {
234+
parsedGroupID, parseErr := strconv.ParseInt(groupIDStr, 10, 64)
235+
if parseErr != nil {
236+
response.ErrorFrom(c, infraerrors.BadRequest("INVALID_GROUP_FILTER", "invalid group filter"))
237+
return
238+
}
239+
if parsedGroupID < 0 {
240+
response.ErrorFrom(c, infraerrors.BadRequest("INVALID_GROUP_FILTER", "invalid group filter"))
241+
return
242+
}
243+
groupID = parsedGroupID
244+
}
230245
}
231246

232247
accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID)

backend/internal/repository/account_repo.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,9 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
474474
if search != "" {
475475
q = q.Where(dbaccount.NameContainsFold(search))
476476
}
477-
if groupID > 0 {
477+
if groupID == service.AccountListGroupUngrouped {
478+
q = q.Where(dbaccount.Not(dbaccount.HasAccountGroups()))
479+
} else if groupID > 0 {
478480
q = q.Where(dbaccount.HasAccountGroupsWith(dbaccountgroup.GroupIDEQ(groupID)))
479481
}
480482

backend/internal/repository/account_repo_integration_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ func (s *AccountRepoSuite) TestListWithFilters() {
214214
accType string
215215
status string
216216
search string
217+
groupID int64
217218
wantCount int
218219
validate func(accounts []service.Account)
219220
}{
@@ -265,6 +266,21 @@ func (s *AccountRepoSuite) TestListWithFilters() {
265266
s.Require().Contains(accounts[0].Name, "alpha")
266267
},
267268
},
269+
{
270+
name: "filter_by_ungrouped",
271+
setup: func(client *dbent.Client) {
272+
group := mustCreateGroup(s.T(), client, &service.Group{Name: "g-ungrouped"})
273+
grouped := mustCreateAccount(s.T(), client, &service.Account{Name: "grouped-account"})
274+
mustCreateAccount(s.T(), client, &service.Account{Name: "ungrouped-account"})
275+
mustBindAccountToGroup(s.T(), client, grouped.ID, group.ID, 1)
276+
},
277+
groupID: service.AccountListGroupUngrouped,
278+
wantCount: 1,
279+
validate: func(accounts []service.Account) {
280+
s.Require().Equal("ungrouped-account", accounts[0].Name)
281+
s.Require().Empty(accounts[0].GroupIDs)
282+
},
283+
},
268284
}
269285

270286
for _, tt := range tests {
@@ -277,7 +293,7 @@ func (s *AccountRepoSuite) TestListWithFilters() {
277293

278294
tt.setup(client)
279295

280-
accounts, _, err := repo.ListWithFilters(ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, tt.platform, tt.accType, tt.status, tt.search, 0)
296+
accounts, _, err := repo.ListWithFilters(ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, tt.platform, tt.accType, tt.status, tt.search, tt.groupID)
281297
s.Require().NoError(err)
282298
s.Require().Len(accounts, tt.wantCount)
283299
if tt.validate != nil {

backend/internal/service/account_service.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ var (
1414
ErrAccountNilInput = infraerrors.BadRequest("ACCOUNT_NIL_INPUT", "account input cannot be nil")
1515
)
1616

17+
const AccountListGroupUngrouped int64 = -1
18+
1719
type AccountRepository interface {
1820
Create(ctx context.Context, account *Account) error
1921
GetByID(ctx context.Context, id int64) (*Account, error)

frontend/src/api/admin/accounts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export async function listWithEtag(
6666
platform?: string
6767
type?: string
6868
status?: string
69+
group?: string
6970
search?: string
7071
lite?: string
7172
},

frontend/src/components/admin/account/AccountTableFilters.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,9 @@ const updateGroup = (value: string | number | boolean | null) => { emit('update:
2626
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' }])
2727
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' }])
2828
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') }])
29-
const gOpts = computed(() => [{ value: '', label: t('admin.accounts.allGroups') }, ...(props.groups || []).map(g => ({ value: String(g.id), label: g.name }))])
29+
const gOpts = computed(() => [
30+
{ value: '', label: t('admin.accounts.allGroups') },
31+
{ value: 'ungrouped', label: t('admin.accounts.ungroupedGroup') },
32+
...(props.groups || []).map(g => ({ value: String(g.id), label: g.name }))
33+
])
3034
</script>

frontend/src/i18n/locales/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,7 @@ export default {
18831883
allTypes: 'All Types',
18841884
allStatus: 'All Status',
18851885
allGroups: 'All Groups',
1886+
ungroupedGroup: 'Ungrouped',
18861887
oauthType: 'OAuth',
18871888
setupToken: 'Setup Token',
18881889
apiKey: 'API Key',

frontend/src/i18n/locales/zh.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,7 @@ export default {
19651965
allTypes: '全部类型',
19661966
allStatus: '全部状态',
19671967
allGroups: '全部分组',
1968+
ungroupedGroup: '未分配分组',
19681969
oauthType: 'OAuth',
19691970
// Schedulable toggle
19701971
schedulable: '参与调度',

frontend/src/views/admin/AccountsView.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ const refreshAccountsIncrementally = async () => {
758758
platform?: string
759759
type?: string
760760
status?: string
761+
group?: string
761762
search?: string
762763
763764
},

0 commit comments

Comments
 (0)