Merge pull request #6659 from ouchihao/codex/issue-77-remove-inactive-groups

fix(accounts): allow removing inactive group assignments
This commit is contained in:
Wesley Liddick
2026-09-08 20:08:40 +08:00
committed by GitHub
3 changed files with 64 additions and 6 deletions
@@ -2851,7 +2851,7 @@
<!-- Group Selection - 仅标准模式显示 -->
<GroupSelector
v-model="form.group_ids"
:groups="groups"
:groups="selectableGroups"
:platform="account?.platform"
:mixed-scheduling="mixedScheduling"
data-tour="account-form-groups"
@@ -2921,6 +2921,7 @@ import type {
Account,
Proxy,
AdminGroup,
Group,
CheckMixedChannelResponse,
OpenAICompactMode,
OpenAIResponsesMode,
@@ -3011,6 +3012,17 @@ const { t } = useI18n()
const appStore = useAppStore()
const browserTimeZone = getBrowserTimeZone()
const selectableGroups = computed(() => {
const groups = new Map<number, Group>(props.groups.map(group => [group.id, group]))
const assignedIds = new Set(props.account?.group_ids ?? [])
for (const group of props.account?.groups ?? []) {
if (assignedIds.has(group.id) && !groups.has(group.id)) {
groups.set(group.id, group)
}
}
return Array.from(groups.values())
})
// Spark 影子账号(parent_account_id 非空):代理恒继承母账号,不可独立编辑(外审 B/P1),
// 故隐藏代理选择器。
const isSparkShadow = computed(() => props.account?.parent_account_id != null)
@@ -302,7 +302,7 @@ function buildOpenAIOAuthParentAccount() {
} as any
}
function mountModal(account = buildAccount()) {
function mountModal(account = buildAccount(), renderGroupSelector = false) {
return mount(EditAccountModal, {
props: {
show: true,
@@ -316,7 +316,7 @@ function mountModal(account = buildAccount()) {
Select: SelectStub,
Icon: true,
ProxySelector: true,
GroupSelector: GroupSelectorStub,
GroupSelector: renderGroupSelector ? false : GroupSelectorStub,
ModelWhitelistSelector: ModelWhitelistSelectorStub
}
}
@@ -372,6 +372,52 @@ describe('EditAccountModal', () => {
wrapper.unmount()
})
it('allows removing assigned inactive groups and undoing the selection before saving', async () => {
authIsSimpleMode.value = false
const account = buildAccount()
const activeGroup = {
id: 1,
name: 'Active group',
platform: 'openai',
status: 'active',
subscription_type: 'standard',
rate_multiplier: 1
}
const inactiveGroup = { ...activeGroup, id: 2, name: 'Paused group', status: 'inactive' }
account.group_ids = [1, 2]
account.groups = [
{ ...activeGroup, name: 'Outdated name' },
inactiveGroup,
inactiveGroup,
{ ...inactiveGroup, id: 3, name: 'Unassigned paused group' }
]
updateAccountMock.mockReset()
checkMixedChannelRiskMock.mockReset()
checkMixedChannelRiskMock.mockResolvedValue({ has_risk: false })
updateAccountMock.mockResolvedValue(account)
const wrapper = mountModal(account, true)
await wrapper.setProps({ groups: [activeGroup] as any })
const selector = wrapper.get('[data-tour="account-form-groups"]')
expect(selector.findAll('input[type="checkbox"]').map(input => input.attributes('value')))
.toEqual(['1', '2'])
expect(selector.text()).toContain('Active group')
expect(selector.text()).not.toContain('Outdated name')
const pausedCheckbox = selector.get<HTMLInputElement>('input[value="2"]')
expect(pausedCheckbox.element.checked).toBe(true)
await pausedCheckbox.setValue(false)
expect(selector.get<HTMLInputElement>('input[value="2"]').element.checked).toBe(false)
await pausedCheckbox.setValue(true)
expect(pausedCheckbox.element.checked).toBe(true)
await pausedCheckbox.setValue(false)
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
expect(updateAccountMock).toHaveBeenCalledTimes(1)
expect(updateAccountMock.mock.calls[0]?.[1]?.group_ids).toEqual([1])
expect(account.group_ids).toEqual([1, 2])
})
it('reopening the same account rehydrates the OpenAI whitelist from props', async () => {
const account = buildAccount()
updateAccountMock.mockReset()
@@ -61,7 +61,7 @@ import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import GroupBadge from './GroupBadge.vue'
import Icon from '@/components/icons/Icon.vue'
import type { AdminGroup, GroupPlatform } from '@/types'
import type { Group, GroupPlatform } from '@/types'
import { useAuthStore } from '@/stores'
const { t } = useI18n()
@@ -69,7 +69,7 @@ const authStore = useAuthStore()
interface Props {
modelValue: number[]
groups: AdminGroup[]
groups: (Group & { account_count?: number })[]
platform?: GroupPlatform // Optional platform filter
mixedScheduling?: boolean // For antigravity accounts: allow anthropic/gemini groups
searchable?: boolean | 'auto'
@@ -91,7 +91,7 @@ const isSearchable = computed(() => {
// Filter groups by platform if specified
const filteredGroups = computed(() => {
let result: AdminGroup[] = authStore.isSimpleMode
let result = authStore.isSimpleMode
? props.groups.filter((g) => g.platform !== 'composite')
: props.groups
if (props.platform) {